Merge branch 'master_MDL-72553_qbank_quiz' of https://github.com/catalyst/moodle-MDL-70329
This commit is contained in:
@@ -1952,6 +1952,7 @@ class core_plugin_manager {
|
||||
'qbank' => [
|
||||
'bulkmove',
|
||||
'comment',
|
||||
'customfields',
|
||||
'deletequestion',
|
||||
'editquestion',
|
||||
'exporttoxml',
|
||||
|
||||
@@ -46,6 +46,10 @@ class qbank extends base {
|
||||
return new \moodle_url('/admin/settings.php', ['section' => 'manageqbanks']);
|
||||
}
|
||||
|
||||
public function get_settings_section_name() {
|
||||
return $this->type . '_' . $this->name;
|
||||
}
|
||||
|
||||
public static function get_plugins($type, $typerootdir, $typeclass, $pluginman): array {
|
||||
global $CFG;
|
||||
|
||||
|
||||
@@ -377,6 +377,10 @@ function question_delete_question($questionid) {
|
||||
// Delete all tag instances.
|
||||
core_tag_tag::remove_all_item_tags('core_question', 'question', $question->id);
|
||||
|
||||
// Delete the custom filed data for the question.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$customfieldhandler->delete_instance($question->id);
|
||||
|
||||
// Now recursively delete all child questions
|
||||
if ($children = $DB->get_records('question',
|
||||
array('parent' => $questionid), '', 'id, qtype')) {
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
namespace mod_quiz\question\bank;
|
||||
|
||||
use coding_exception;
|
||||
|
||||
/**
|
||||
* Subclass to customise the view of the question bank for the quiz editing screen.
|
||||
*
|
||||
@@ -58,32 +56,77 @@ class custom_view extends \core_question\local\bank\view {
|
||||
$this->quiz = $quiz;
|
||||
}
|
||||
|
||||
protected function wanted_columns(): array {
|
||||
$quizquestionbankcolumns = [
|
||||
'mod_quiz\\question\\bank\\add_action_column',
|
||||
'core_question\\local\\bank\\checkbox_column',
|
||||
'qbank_viewquestiontype\\question_type_column',
|
||||
'mod_quiz\\question\\bank\\question_name_text_column',
|
||||
'qbank_previewquestion\\preview_action_column',
|
||||
protected function get_question_bank_plugins(): array {
|
||||
$questionbankclasscolumns = [];
|
||||
$corequestionbankcolumns = [
|
||||
'add_action_column',
|
||||
'checkbox_column',
|
||||
'question_type_column',
|
||||
'question_name_text_column',
|
||||
'preview_action_column'
|
||||
];
|
||||
|
||||
foreach ($quizquestionbankcolumns as $fullname) {
|
||||
if (!class_exists($fullname)) {
|
||||
throw new coding_exception('Invalid quiz question bank column', $fullname);
|
||||
}
|
||||
$this->requiredcolumns[$fullname] = new $fullname($this);
|
||||
if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) {
|
||||
$corequestionbankcolumns[] = 'question_text_row';
|
||||
}
|
||||
return $this->requiredcolumns;
|
||||
|
||||
foreach ($corequestionbankcolumns as $fullname) {
|
||||
$shortname = $fullname;
|
||||
if (class_exists('mod_quiz\\question\\bank\\' . $fullname)) {
|
||||
$fullname = 'mod_quiz\\question\\bank\\' . $fullname;
|
||||
$questionbankclasscolumns[$shortname] = new $fullname($this);
|
||||
} else if (class_exists('core_question\\local\\bank\\' . $fullname)) {
|
||||
$fullname = 'core_question\\local\\bank\\' . $fullname;
|
||||
$questionbankclasscolumns[$shortname] = new $fullname($this);
|
||||
} else {
|
||||
$questionbankclasscolumns[$shortname] = '';
|
||||
}
|
||||
}
|
||||
$plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php');
|
||||
foreach ($plugins as $componentname => $plugin) {
|
||||
$pluginentrypointobject = new $plugin();
|
||||
$plugincolumnobjects = $pluginentrypointobject->get_question_columns($this);
|
||||
// Don't need the plugins without column objects.
|
||||
if (empty($plugincolumnobjects)) {
|
||||
unset($plugins[$componentname]);
|
||||
continue;
|
||||
}
|
||||
foreach ($plugincolumnobjects as $columnobject) {
|
||||
$columnname = $columnobject->get_column_name();
|
||||
foreach ($corequestionbankcolumns as $key => $corequestionbankcolumn) {
|
||||
if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) {
|
||||
unset($questionbankclasscolumns[$columnname]);
|
||||
continue;
|
||||
}
|
||||
// Check if it has custom preference selector to view/hide.
|
||||
if ($columnobject->has_preference() && !$columnobject->get_preference()) {
|
||||
continue;
|
||||
}
|
||||
if ($corequestionbankcolumn === $columnname) {
|
||||
$questionbankclasscolumns[$columnname] = $columnobject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mitigate the error in case of any regression.
|
||||
foreach ($questionbankclasscolumns as $shortname => $questionbankclasscolumn) {
|
||||
if (empty($questionbankclasscolumn)) {
|
||||
unset($questionbankclasscolumns[$shortname]);
|
||||
}
|
||||
}
|
||||
|
||||
return $questionbankclasscolumns;
|
||||
}
|
||||
|
||||
protected function heading_column(): string {
|
||||
return 'mod_quiz\\question\\bank\\question_name_text_column';
|
||||
return 'question_name_text_column';
|
||||
}
|
||||
|
||||
protected function default_sort(): array {
|
||||
return [
|
||||
'qbank_viewquestiontype\\question_type_column' => 1,
|
||||
'mod_quiz\\question\\bank\\question_name_text_column' => 1,
|
||||
'question_type_column' => 1,
|
||||
'question_name_text_column' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ Feature: Edit quiz page - adding things
|
||||
And I open the "last" add to quiz menu
|
||||
And I follow "from question bank"
|
||||
Then the "Add selected questions to the quiz" "button" should be disabled
|
||||
And I click on "Essay 03" "checkbox"
|
||||
And I click on "Select" "checkbox" in the "Essay 03" "table_row"
|
||||
Then the "Add selected questions to the quiz" "button" should be enabled
|
||||
And I click on "Add to quiz" "link" in the "Essay 03" "table_row"
|
||||
And I should see "Essay 03" on quiz page "1"
|
||||
|
||||
@@ -121,7 +121,11 @@ Feature: Adding questions to a quiz from the question bank
|
||||
And I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
|
||||
When I open the "last" add to quiz menu
|
||||
And I follow "from question bank"
|
||||
And I click on "Sort by Question ascending" "link"
|
||||
Then "question 01 name" "text" should appear before "question 02 name" "text"
|
||||
And I click on "Sort by Question descending" "link"
|
||||
And "question 03 name" "text" should appear before "question 01 name" "text"
|
||||
And I follow "Sort by Question type ascending"
|
||||
Then "question 01 name" "text" should appear before "question 03 name" "text"
|
||||
And I follow "Sort by Question type descending"
|
||||
Then "question 03 name" "checkbox" should appear before "question 01 name" "checkbox"
|
||||
And I follow "Sort by Question ascending"
|
||||
And "question 01 name" "checkbox" should appear before "question 02 name" "checkbox"
|
||||
Then "question 03 name" "text" should appear before "question 01 name" "text"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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 information to backup question custom field.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class backup_qbank_customfields_plugin extends \backup_qbank_plugin {
|
||||
|
||||
/**
|
||||
* Returns the comment information to attach to question element.
|
||||
*
|
||||
* @return backup_plugin_element The backup plugin element
|
||||
*/
|
||||
protected function define_question_plugin_structure(): backup_plugin_element {
|
||||
|
||||
// Define the virtual plugin element with the condition to fulfill.
|
||||
$plugin = $this->get_plugin_element();
|
||||
|
||||
// Create one standard named plugin element (the visible container).
|
||||
$pluginwrapper = new backup_nested_element($this->get_recommended_name());
|
||||
|
||||
// Connect the visible container ASAP.
|
||||
$plugin->add_child($pluginwrapper);
|
||||
|
||||
$customfields = new backup_nested_element('customfields');
|
||||
$customfield = new backup_nested_element('customfield', ['id'], ['shortname', 'type', 'value', 'valueformat']);
|
||||
|
||||
$pluginwrapper->add_child($customfields);
|
||||
$customfields->add_child($customfield);
|
||||
|
||||
$customfield->set_source_sql("SELECT cfd.id, cff.shortname, cff.type, cfd.value, cfd.valueformat
|
||||
FROM {customfield_data} cfd
|
||||
JOIN {customfield_field} cff ON cff.id = cfd.fieldid
|
||||
JOIN {customfield_category} cfc ON cfc.id = cff.categoryid
|
||||
WHERE cfc.component = 'qbank_customfields'
|
||||
AND cfc.area = 'question'
|
||||
AND cfd.instanceid = ?",
|
||||
[
|
||||
backup::VAR_PARENTID
|
||||
]);
|
||||
|
||||
// Don't need to annotate ids nor files.
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Restore plugin class that provides the necessary information needed to restore comments for questions.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class restore_qbank_customfields_plugin extends restore_qbank_plugin {
|
||||
|
||||
/**
|
||||
* Returns the paths to be handled by the plugin at question level.
|
||||
*
|
||||
* @return restore_path_element[] The restore path element array.
|
||||
*/
|
||||
protected function define_question_plugin_structure(): array {
|
||||
return [new restore_path_element('customfield', $this->get_pathfor('/customfields/customfield'))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the question custom field element.
|
||||
*
|
||||
* @param array $data The custom field data to restore.
|
||||
*/
|
||||
public function process_customfield(array $data) {
|
||||
global $DB;
|
||||
|
||||
$newquestion = $this->get_new_parentid('question');
|
||||
|
||||
if (!empty($data->contextid) && $newcontextid = $this->get_mappingid('context', $data->contextid)) {
|
||||
$fieldcontextid = $newcontextid;
|
||||
} else {
|
||||
// Get the category, so we can then later get the context.
|
||||
$categoryid = $this->get_new_parentid('question_category');
|
||||
if (empty($this->cachedcategory) || $this->cachedcategory->id != $categoryid) {
|
||||
$this->cachedcategory = $DB->get_record('question_categories', ['id' => $categoryid]);
|
||||
}
|
||||
$fieldcontextid = $this->cachedcategory->contextid;
|
||||
}
|
||||
|
||||
$data['newquestion'] = $newquestion;
|
||||
$data['fieldcontextid'] = $fieldcontextid;
|
||||
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$customfieldhandler->restore_instance_data_from_backup($this->task, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace qbank_customfields;
|
||||
|
||||
use core_question\local\bank\column_base;
|
||||
|
||||
/**
|
||||
* A column type for the name of the question creator.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class custom_field_column extends column_base {
|
||||
|
||||
/** @var \core_customfield\field_controller The custom field this column is displaying. */
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param view $qbank the question bank view we are helping to render.
|
||||
* @param \core_customfield\field_controller $field The custom field this column is displaying.
|
||||
*/
|
||||
public function __construct(\core_question\local\bank\view $qbank, \core_customfield\field_controller $field) {
|
||||
parent::__construct($qbank);
|
||||
$this->field = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal name for this column. Used as a CSS class name,
|
||||
* and to store information about the current sort. Must match PARAM_ALPHA.
|
||||
*
|
||||
* @return string column name.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'customfield';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this column. This must be unique.
|
||||
* When using the inherited class to make many columns from one parent,
|
||||
* ensure each instance returns a unique value.
|
||||
*
|
||||
* @return string The unique name;
|
||||
*/
|
||||
public function get_column_name(): string {
|
||||
return 'custom_field_column\\' . $this->field->get('shortname');
|
||||
}
|
||||
|
||||
/**
|
||||
* Title for this column. Not used if is_sortable returns an array.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_title(): string {
|
||||
return $this->field->get_formatted_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the contents of this column.
|
||||
*
|
||||
* @param object $question the row from the $question table, augmented with extra information.
|
||||
* @param string $rowclasses CSS class names that should be applied to this row of output.
|
||||
*/
|
||||
protected function display_content($question, $rowclasses): void {
|
||||
$fieldhandler = $this->field->get_handler();
|
||||
if ($fieldhandler->can_view($this->field, $question->id)) {
|
||||
$fielddata = $fieldhandler->get_field_data($this->field, $question->id);
|
||||
echo $fieldhandler->display_custom_field_table($fielddata);
|
||||
} else {
|
||||
echo '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace qbank_customfields\customfield;
|
||||
|
||||
use core_customfield\api;
|
||||
use core_customfield\field_controller;
|
||||
use core_customfield\output\field_data;
|
||||
|
||||
/**
|
||||
* Question handler for custom fields.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_handler extends \core_customfield\handler {
|
||||
|
||||
/**
|
||||
* @var question_handler
|
||||
*/
|
||||
static protected $singleton;
|
||||
|
||||
/**
|
||||
* @var \context
|
||||
*/
|
||||
protected $parentcontext;
|
||||
|
||||
/** @var int Field is displayed in the question display and question preview, visible to everybody */
|
||||
const VISIBLETOALL = 2;
|
||||
/** @var int Field is displayed in the question display and question preview but only for "teachers" */
|
||||
const VISIBLETOTEACHERS = 1;
|
||||
/** @var int Field is not displayed in the question display and question preview */
|
||||
const NOTVISIBLE = 0;
|
||||
|
||||
/**
|
||||
* Creates the custom field handler and returns a singleton.
|
||||
* Itemid is always zero as the custom fields are the same
|
||||
* for every question across the system.
|
||||
*
|
||||
* @param int $itemid Always zero.
|
||||
* @return \qbank_customfields\customfield\question_handler
|
||||
*/
|
||||
public static function create(int $itemid = 0) : \core_customfield\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('qbank/customfields:configurecustomfields', $this->get_configuration_context());
|
||||
}
|
||||
|
||||
/**
|
||||
* The current user can edit custom fields for the given question.
|
||||
*
|
||||
* @param field_controller $field
|
||||
* @param int $instanceid id of the question 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 {
|
||||
if ($instanceid) {
|
||||
$context = $this->get_instance_context($instanceid);
|
||||
} else {
|
||||
$context = $this->get_parent_context();
|
||||
}
|
||||
|
||||
return (!$field->get_configdata_property('locked') ||
|
||||
has_capability('qbank/customfields:changelockedcustomfields', $context));
|
||||
}
|
||||
|
||||
/**
|
||||
* The current user can view custom fields for the given question.
|
||||
*
|
||||
* @param field_controller $field
|
||||
* @param int $instanceid id of the question 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 if ($visibility == self::VISIBLETOTEACHERS) {
|
||||
return has_capability('qbank/customfields:viewhiddencustomfields', $this->get_instance_context($instanceid));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can view custom field in their given context.
|
||||
* This determines if the user can see the field at all not just the field for
|
||||
* a particular instance.
|
||||
* Used primarily in showing or not the field in the question bank table.
|
||||
*
|
||||
* @param field_controller $field The field trying to be viewed.
|
||||
* @param context $context The context the field is being displayed in.
|
||||
* @return bool true if the current can edit custom fields, false otherwise.
|
||||
*/
|
||||
public function can_view_type(field_controller $field, \context $context) : bool {
|
||||
$visibility = $field->get_configdata_property('visibility');
|
||||
if ($visibility == self::NOTVISIBLE) {
|
||||
return false;
|
||||
} else if ($visibility == self::VISIBLETOTEACHERS) {
|
||||
return has_capability('qbank/customfields:viewhiddencustomfields', $context);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets parent context for the question.
|
||||
*
|
||||
* This may be needed when question is being created, there is no question context but we need to check capabilities
|
||||
*
|
||||
* @param \context $context
|
||||
*/
|
||||
public function set_parent_context(\context $context): void {
|
||||
$this->parentcontext = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent context for the question.
|
||||
*
|
||||
* @return \context
|
||||
*/
|
||||
protected function get_parent_context() : \context {
|
||||
if ($this->parentcontext) {
|
||||
return $this->parentcontext;
|
||||
} else {
|
||||
return \context_system::instance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 page for the fields for the question custom fields.
|
||||
*
|
||||
* @return \moodle_url The URL to configure custom fields for this component
|
||||
*/
|
||||
public function get_configuration_url() : \moodle_url {
|
||||
return new \moodle_url('/question/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
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_instance_context(int $instanceid = 0) : \context {
|
||||
if ($instanceid > 0) {
|
||||
$questiondata = \question_bank::load_question_data($instanceid);
|
||||
$contextid = $questiondata->contextid;
|
||||
$context = \context::instance_by_id($contextid);
|
||||
return $context;
|
||||
} else {
|
||||
throw new \coding_exception('Instance id must be provided.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a field and instance id get all the filed data.
|
||||
*
|
||||
* @param field_controller $field The field to get the data for.
|
||||
* @param int $instanceid The instance id to get the data for.
|
||||
* @return \core_customfield\data_controller The fetched data.
|
||||
*/
|
||||
public function get_field_data(\core_customfield\field_controller $field, int $instanceid): \core_customfield\data_controller {
|
||||
$fields = [$field->get('id') => $field];
|
||||
$fieldsdata = api::get_instance_fields_data($fields, $instanceid);
|
||||
return $fieldsdata[$field->get('id')];
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given instance id (question id) get the categories and the
|
||||
* fields with any data. Return an array of categories containing an
|
||||
* array of field names and values that is ready to be passed to a renderer.
|
||||
*
|
||||
* @param int $instanceid The instance id to get the data for.
|
||||
* @return array $cfdata The fetched data
|
||||
*/
|
||||
public function get_categories_fields_data(int $instanceid): array {
|
||||
// Prepare custom fields data.
|
||||
$instancedata = $this->get_instance_data($instanceid);
|
||||
|
||||
$cfdata = [];
|
||||
|
||||
foreach ($instancedata as $instance) {
|
||||
$field = $instance->get_field();
|
||||
|
||||
if ($this->can_view($field, $instanceid)) {
|
||||
$category = $instance->get_field()->get_category()->get('name');
|
||||
$fieldname = $field->get_formatted_name();
|
||||
$fieldvalue = $this->get_field_data($field, $instanceid)->export_value();
|
||||
$cfdata[$category][] = ['name' => $fieldname, 'value' => $fieldvalue];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $cfdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom data for the given field
|
||||
* and render HTML ready for display in question table.
|
||||
*
|
||||
* @param object $fielddata The field data used for display.
|
||||
* @return string The HTML to display in the table column.
|
||||
*/
|
||||
public function display_custom_field_table(object $fielddata) : string {
|
||||
global $PAGE;
|
||||
|
||||
$output = $PAGE->get_renderer('qbank_customfields');
|
||||
$outputdata = new field_data($fielddata);
|
||||
|
||||
return $output->render_for_table($outputdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the custom field category and filed data as HTML ready for display.
|
||||
*
|
||||
* @param array $catfielddata Array of categories and field names and values.
|
||||
* @return string The HTML to display.
|
||||
*/
|
||||
public function display_custom_categories_fields(array $catfielddata) : string {
|
||||
global $PAGE;
|
||||
$output = $PAGE->get_renderer('qbank_customfields');
|
||||
|
||||
return $output->render_for_preview($catfielddata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom controls to the field configuration form that will be saved.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform The form to add the custom fields to.
|
||||
*/
|
||||
public function config_form_definition(\MoodleQuickForm $mform): void {
|
||||
$mform->addElement('header', 'question_handler_header',
|
||||
get_string('customfieldsettings', 'qbank_customfields'));
|
||||
$mform->setExpanded('question_handler_header', true);
|
||||
|
||||
// If field is locked.
|
||||
$mform->addElement('selectyesno', 'configdata[locked]',
|
||||
get_string('customfield_islocked', 'qbank_customfields'));
|
||||
$mform->addHelpButton('configdata[locked]', 'customfield_islocked', 'qbank_customfields');
|
||||
|
||||
// Field data visibility.
|
||||
$visibilityoptions = [
|
||||
self::VISIBLETOALL => get_string('customfield_visibletoall', 'qbank_customfields'),
|
||||
self::VISIBLETOTEACHERS => get_string('customfield_visibletoteachers', 'qbank_customfields'),
|
||||
self::NOTVISIBLE => get_string('customfield_notvisible', 'qbank_customfields')
|
||||
];
|
||||
$mform->addElement('select', 'configdata[visibility]',
|
||||
get_string('customfield_visibility', 'qbank_customfields'),
|
||||
$visibilityoptions);
|
||||
$mform->addHelpButton(
|
||||
'configdata[visibility]', 'customfield_visibility', 'qbank_customfields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates the question custom field data when restoring from a backup.
|
||||
*
|
||||
* @param \restore_task $task
|
||||
* @param array $data
|
||||
*/
|
||||
public function restore_instance_data_from_backup(\restore_task $task, array $data): void {
|
||||
|
||||
$editablefields = $this->get_editable_fields($data['newquestion']);
|
||||
$records = api::get_instance_fields_data($editablefields, $data['newquestion']);
|
||||
$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', $data['fieldcontextid']);
|
||||
$d->save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace qbank_customfields\output;
|
||||
|
||||
/**
|
||||
* Class renderer.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class renderer extends \plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Render custom field data for table display.
|
||||
*
|
||||
* @param object $fielddata The field data to display.
|
||||
* @return string The rendered HTML.
|
||||
*/
|
||||
public function render_for_table(object $fielddata): string {
|
||||
$context = $fielddata->export_for_template($this);
|
||||
return $this->render_from_template('qbank_customfields/table_display', $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom filed data for table display.
|
||||
*
|
||||
* @param array $catfielddata The category and field data.
|
||||
* @return string The rendered HTML.
|
||||
*/
|
||||
public function render_for_preview(array $catfielddata): string {
|
||||
$context = ['categories' => []];
|
||||
|
||||
foreach ($catfielddata as $key => $value) {
|
||||
$context['categories'][] = [
|
||||
'catname' => $key,
|
||||
'fields' => $value
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return $this->render_from_template('qbank_customfields/preview_display', $context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace qbank_customfields;
|
||||
|
||||
use core_question\local\bank\plugin_features_base;
|
||||
use core_question\local\bank\view;
|
||||
use qbank_customfields\customfield\question_handler;
|
||||
|
||||
/**
|
||||
* Class plugin_feature is the entrypoint for the columns.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Ghaly Marc-Alexandre <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class plugin_feature extends plugin_features_base {
|
||||
|
||||
/**
|
||||
* This method will return the array of objects to be rendered as a prt of question bank columns/actions.
|
||||
*
|
||||
* @param view $qbank
|
||||
* @return array
|
||||
*/
|
||||
public function get_question_columns(view $qbank): array {
|
||||
// We make a column for each custom field and load the data into it.
|
||||
$columns = [];
|
||||
|
||||
// First get all the available question custom fields.
|
||||
$customfieldhandler = question_handler::create();
|
||||
$fields = $customfieldhandler->get_fields();
|
||||
$context = $qbank->get_most_specific_context();
|
||||
|
||||
// Iterate through the fields initialising a column for each.
|
||||
// We don't need to know the values that questions have at this stage.
|
||||
foreach ($fields as $field) {
|
||||
if ($customfieldhandler->can_view_type($field, $context)) {
|
||||
$customfieldcolumn = new custom_field_column($qbank, $field);
|
||||
$columns[] = $customfieldcolumn;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace qbank_customfields\privacy;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for qbank_customfields implementing null_provider.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\null_provider {
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Manage question custom fields
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../../..//config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
|
||||
admin_externalpage_setup('qbank_customfields');
|
||||
|
||||
$output = $PAGE->get_renderer('core_customfield');
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$outputpage = new \core_customfield\output\management($customfieldhandler);
|
||||
|
||||
echo $output->header(),
|
||||
$output->heading(new lang_string('pluginname', 'qbank_customfields')),
|
||||
$output->render($outputpage),
|
||||
$output->footer();
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Plugin capabilities are defined here.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @category access
|
||||
* @copyright 2021 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$capabilities = [
|
||||
// Controls if a user can can change the value of a locked custom field.
|
||||
'qbank/customfields:changelockedcustomfields' => [
|
||||
'riskbitmask' => RISK_SPAM,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => [
|
||||
'manager' => CAP_ALLOW
|
||||
],
|
||||
],
|
||||
|
||||
// Controls if a user can configure question custom fields.
|
||||
'qbank/customfields:configurecustomfields' => [
|
||||
'riskbitmask' => RISK_SPAM,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'clonepermissionsfrom' => 'moodle/site:config'
|
||||
],
|
||||
|
||||
// Controls if a user can view the content of hidden custom fields.
|
||||
'qbank/customfields:viewhiddencustomfields' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => [
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Strings for component qbank_customfields, language 'en'.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Question custom fields';
|
||||
$string['privacy:metadata'] = 'Question custom fields plugin does not store any user data.';
|
||||
|
||||
$string['customfield'] = 'Question custom fields';
|
||||
$string['customfield_islocked'] = 'Locked';
|
||||
$string['customfield_islocked_help'] = 'If the field is locked, only users with the capability to change locked custom fields will be able to change it in the question settings.';
|
||||
$string['customfield_notvisible'] = 'Nobody';
|
||||
$string['customfield_visibility'] = 'Visible to';
|
||||
$string['customfield_visibility_help'] = 'This setting determines who can view the custom field name and value.';
|
||||
$string['customfield_visibletoall'] = 'Everyone';
|
||||
$string['customfield_visibletoteachers'] = 'Teachers';
|
||||
$string['customfieldsettings'] = 'Common question custom fields settings';
|
||||
$string['customfields:changelockedcustomfields'] = 'Change locked custom fields';
|
||||
$string['customfields:configurecustomfields'] = 'Configure custom fields';
|
||||
$string['customfields:viewhiddencustomfields'] = 'View hidden custom fields';
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Helper functions and callbacks.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Comment content for callbacks.
|
||||
*
|
||||
* @param question_definition $question
|
||||
* @param int $courseid
|
||||
* @return string
|
||||
*/
|
||||
function qbank_customfields_preview_display(question_definition $question, int $courseid): string {
|
||||
// Prepare custom fields data.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$catfielddata = $customfieldhandler->get_categories_fields_data($question->id);
|
||||
return $customfieldhandler->display_custom_categories_fields($catfielddata);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Plugin administration pages are defined here.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
// Settings for question custom fields.
|
||||
$settings = null;
|
||||
$ADMIN->add('qbanksettings',
|
||||
new admin_externalpage('qbank_customfields',
|
||||
new lang_string('pluginname', 'qbank_customfields'),
|
||||
$CFG->wwwroot . '/question/bank/customfields/customfield.php',
|
||||
['qbank/customfields:configurecustomfields']
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{{!
|
||||
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 qbank_customfields/preview_display
|
||||
|
||||
Example context (json):
|
||||
{"categories":[
|
||||
{ "catname":"Other fields",
|
||||
"fields":[
|
||||
{"name":"checkit", "value":"No"},
|
||||
{"name":"datetime", "value":"1 October 2021"}
|
||||
]
|
||||
}
|
||||
]}
|
||||
}}
|
||||
<h3>{{#str}} customfield, qbank_customfields {{/str}}</h3>
|
||||
{{#categories}}
|
||||
<h4>{{catname}}</h4>
|
||||
{{#fields}}
|
||||
<div class="customfield customfield_{{type}} customfield_{{shortname}}">
|
||||
<span class="customfieldname">{{{name}}}</span><span class="customfieldseparator">: </span><span class="customfieldvalue">{{{value}}}</span>
|
||||
</div>
|
||||
{{/fields}}
|
||||
{{/categories}}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!
|
||||
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 qbank_customfields/table_display
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"ftype" : "text",
|
||||
"shortname" : "nickname",
|
||||
"value" : "Star Lord"
|
||||
}
|
||||
}}
|
||||
{{#hasvalue}}
|
||||
<div class="customfield customfield_{{type}} customfield_{{shortname}}">
|
||||
<span class="customfieldvalue">{{{value}}}</span>
|
||||
</div>
|
||||
{{/hasvalue}}
|
||||
@@ -0,0 +1,60 @@
|
||||
@core @core_question @core_customfield @qbank_customfields @javascript
|
||||
Feature: A teacher can edit question with custom fields
|
||||
In order to improve my questions
|
||||
As a teacher
|
||||
I need to be able to edit questions and add extra metadata via custom fields
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | weeks |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| Category for test | qbank_customfields | question | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Field 1 | Category for test | text | f1 |
|
||||
And the following "activity" exists:
|
||||
| activity | quiz |
|
||||
| course | C1 |
|
||||
| idnumber | 00001 |
|
||||
| name | Test quiz name |
|
||||
| intro | Test quiz description |
|
||||
| section | 1 |
|
||||
| grade | 10 |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "True/False" question to the "Test quiz name" quiz with:
|
||||
| Question name | First question |
|
||||
| Question text | Answer the first question |
|
||||
| General feedback | Thank you, this is the general feedback |
|
||||
| Correct answer | False |
|
||||
| Feedback for the response 'True'. | So you think it is true |
|
||||
| Feedback for the response 'False'. | So you think it is false |
|
||||
And I am on the "Test quiz name" "quiz activity" page
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
|
||||
Scenario: Edit a previously created question and see the custom field in the overview table and in the question preview.
|
||||
When I choose "Edit question" action for "First question" in the question bank
|
||||
And I should see "Category for test"
|
||||
And I click on "Expand all" "link"
|
||||
And I should see "Field 1"
|
||||
And I set the following fields to these values:
|
||||
| Field 1 | custom field text |
|
||||
And I press "id_submitbutton"
|
||||
And I should see "First question"
|
||||
And I should see "custom field text"
|
||||
And I choose "Preview" action for "First question" in the question bank
|
||||
And I should see "Field 1"
|
||||
Then I should see "custom field text"
|
||||
|
||||
Scenario: Preview a previously created question with custom fields set with empty values
|
||||
When I choose "Preview" action for "First question" in the question bank
|
||||
And I should see "Field 1"
|
||||
Then I should not see "custom field text"
|
||||
@@ -0,0 +1,98 @@
|
||||
@core @core_question @core_customfield @qbank_customfields @javascript
|
||||
Feature: Site administrators can manage categories for question custom fields
|
||||
In order to have additional data in questions
|
||||
As a site site administrator
|
||||
I need to create, edit, remove and sort question custom field categories
|
||||
|
||||
Scenario: Create a category for custom question fields
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
And I press "Add a new category"
|
||||
And I wait until the page is ready
|
||||
Then I should see "Other fields" in the "#customfield_catlist" "css_element"
|
||||
|
||||
Scenario: Edit a category name for custom question fields
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| Category for test | qbank_customfields | question | 0 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
And I set the field "Edit category name" in the "//div[contains(@class,'categoryinstance') and contains(.,'Category for test')]" "xpath_element" to "Good fields"
|
||||
Then I should not see "Category for test" in the "#customfield_catlist" "css_element"
|
||||
And "New value for Category for test" "field" should not exist
|
||||
And I should see "Good fields" in the "#customfield_catlist" "css_element"
|
||||
|
||||
Scenario: Delete a category for custom question fields
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| Category for test | qbank_customfields | question | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Field 1 | Category for test | text | f1 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
And I click on "[data-role='deletecategory']" "css_element"
|
||||
And I click on "Yes" "button" in the "Confirm" "dialogue"
|
||||
And I wait until the page is ready
|
||||
And I wait until "Test category" "text" does not exist
|
||||
Then I should not see "Test category" in the "#customfield_catlist" "css_element"
|
||||
|
||||
Scenario: Move field in the question custom fields to another category
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| Category1 | qbank_customfields | question | 0 |
|
||||
| Category2 | qbank_customfields | question | 0 |
|
||||
| Category3 | qbank_customfields | question | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Field1 | Category1 | text | f1 |
|
||||
| Field2 | Category2 | text | f2 |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
Then "Field1" "text" should appear after "Category1" "text"
|
||||
And "Category2" "text" should appear after "Field1" "text"
|
||||
And "Field2" "text" should appear after "Category2" "text"
|
||||
And "Category3" "text" should appear after "Field2" "text"
|
||||
And I press "Move \"Field1\""
|
||||
And I follow "To the top of category Category2"
|
||||
And "Category2" "text" should appear after "Category1" "text"
|
||||
And "Field1" "text" should appear after "Category2" "text"
|
||||
And "Field2" "text" should appear after "Field1" "text"
|
||||
And "Category3" "text" should appear after "Field2" "text"
|
||||
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
And "Category2" "text" should appear after "Category1" "text"
|
||||
And "Field1" "text" should appear after "Category2" "text"
|
||||
And "Field2" "text" should appear after "Field1" "text"
|
||||
And "Category3" "text" should appear after "Field2" "text"
|
||||
And I press "Move \"Field1\""
|
||||
And I follow "After field Field2"
|
||||
And "Field1" "text" should appear after "Field2" "text"
|
||||
|
||||
Scenario: Reorder question custom field categories
|
||||
Given the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| Category1 | qbank_customfields | question | 0 |
|
||||
| Category2 | qbank_customfields | question | 0 |
|
||||
| Category3 | qbank_customfields | question | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname |
|
||||
| Field1 | Category1 | text | f1 |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
Then "Field1" "text" should appear after "Category1" "text"
|
||||
And "Category2" "text" should appear after "Field1" "text"
|
||||
And "Category3" "text" should appear after "Category2" "text"
|
||||
And I press "Move \"Category2\""
|
||||
And I follow "After \"Category3\""
|
||||
And "Field1" "text" should appear after "Category1" "text"
|
||||
And "Category3" "text" should appear after "Field1" "text"
|
||||
And "Category2" "text" should appear after "Category3" "text"
|
||||
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
|
||||
And "Field1" "text" should appear after "Category1" "text"
|
||||
And "Category3" "text" should appear after "Field1" "text"
|
||||
And "Category2" "text" should appear after "Category3" "text"
|
||||
And I press "Move \"Category2\""
|
||||
And I follow "After \"Category1\""
|
||||
And "Field1" "text" should appear after "Category1" "text"
|
||||
And "Category2" "text" should appear after "Field1" "text"
|
||||
And "Category3" "text" should appear after "Category2" "text"
|
||||
@@ -0,0 +1,67 @@
|
||||
@core @core_quiz @core_customfield @qbank_customfields @javascript @xxpriority
|
||||
Feature: The visibility of question custom fields control where they are displayed
|
||||
In order to display custom fields in a quiz
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | weeks |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "custom field categories" exist:
|
||||
| name | component | area | itemid |
|
||||
| Category for test | qbank_customfields | question | 0 |
|
||||
And the following "custom fields" exist:
|
||||
| name | category | type | shortname | configdata |
|
||||
| Field 1 | Category for test | text | f1 | {"visibility":"2"} |
|
||||
| Field 2 | Category for test | text | f2 | {"visibility":"2"} |
|
||||
| Field 3 | Category for test | text | f3 | {"visibility":"2","defaultvalue":"secret"} |
|
||||
And the following "activity" exists:
|
||||
| activity | quiz |
|
||||
| course | C1 |
|
||||
| idnumber | 00001 |
|
||||
| name | Test quiz name |
|
||||
| intro | Test quiz description |
|
||||
| section | 1 |
|
||||
| grade | 10 |
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "True/False" question to the "Test quiz name" quiz with:
|
||||
| Question name | First question |
|
||||
| Question text | Answer the first question |
|
||||
| General feedback | Thank you, this is the general feedback |
|
||||
| Correct answer | False |
|
||||
| Feedback for the response 'True'. | So you think it is true |
|
||||
| Feedback for the response 'False'. | So you think it is false |
|
||||
And I am on the "Test quiz name" "quiz activity" page
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
|
||||
@javascript
|
||||
Scenario: Display custom question fields to teachers based on their visibility.
|
||||
When I choose "Edit question" action for "First question" in the question bank
|
||||
And I should see "Category for test"
|
||||
And I click on "Expand all" "link"
|
||||
And I should see "Field 1"
|
||||
And I should see "Field 2"
|
||||
And I should see "Field 3"
|
||||
And I set the following fields to these values:
|
||||
| Field 1 | custom field text one|
|
||||
| Field 2 | custom field text two|
|
||||
And I press "id_submitbutton"
|
||||
And I should see "Field 1"
|
||||
And I should see "custom field text one"
|
||||
And I should see "Field 2"
|
||||
And I should see "custom field text two"
|
||||
And I should see "Field 3"
|
||||
And I should see "secret"
|
||||
And I choose "Preview" action for "First question" in the question bank
|
||||
And I should see "Field 1"
|
||||
And I should see "custom field text one"
|
||||
And I should see "Field 2"
|
||||
And I should see "custom field text two"
|
||||
And I should see "Field 3"
|
||||
Then I should see "secret"
|
||||
@@ -0,0 +1,306 @@
|
||||
<?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/>.
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
||||
|
||||
/**
|
||||
* Class qbank_customfields_customfield_testcase
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qbank_customfields_customfield_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* @var array Data object for generating a question.
|
||||
*/
|
||||
protected $question1data;
|
||||
|
||||
/**
|
||||
* @var array Data object for generating a question.
|
||||
*/
|
||||
protected $question2data;
|
||||
|
||||
/**
|
||||
* @var component_generator_base Question Generator.
|
||||
*/
|
||||
protected $qgen;
|
||||
|
||||
/**
|
||||
* @var core_course_category Course category.
|
||||
*/
|
||||
protected $category;
|
||||
|
||||
/**
|
||||
* @var stdClass Course object.
|
||||
*/
|
||||
protected $course;
|
||||
|
||||
/**
|
||||
* @var int Timestamp to use in tests.
|
||||
*/
|
||||
protected $testnow = 1632278491;
|
||||
|
||||
/**
|
||||
* Helper to assist with setting up custom fields.
|
||||
* This is creating custom field category and the fields, not adding instance field data.
|
||||
*/
|
||||
protected function setup_custom_fields(): void {
|
||||
|
||||
$dg = self::getDataGenerator();
|
||||
$data = new \stdClass();
|
||||
$data->component = 'qbank_customfields';
|
||||
$data->area = 'question';
|
||||
|
||||
$catid = $dg->create_custom_field_category($data)->get('id');
|
||||
$dg->create_custom_field(['categoryid' => $catid, 'type' => 'text', 'shortname' => 'f1']);
|
||||
$dg->create_custom_field(['categoryid' => $catid, 'type' => 'checkbox', 'shortname' => 'f2']);
|
||||
$dg->create_custom_field(['categoryid' => $catid, 'type' => 'date', 'shortname' => 'f3',
|
||||
'configdata' => ['startyear' => 2000, 'endyear' => 3000, 'includetime' => 1]]);
|
||||
$dg->create_custom_field(['categoryid' => $catid, 'type' => 'select', 'shortname' => 'f4',
|
||||
'configdata' => ['options' => "a\nb\nc"]]);
|
||||
$dg->create_custom_field(['categoryid' => $catid, 'type' => 'textarea', 'shortname' => 'f5']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to assist with setting up questions used in tests.
|
||||
*/
|
||||
protected function setup_questions(): void {
|
||||
// Question initial set up.
|
||||
$this->category = $this->getDataGenerator()->create_category();
|
||||
$this->course = $this->getDataGenerator()->create_course(['category' => $this->category->id]);
|
||||
$context = context_coursecat::instance($this->category->id);
|
||||
$this->qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat = $this->qgen->create_question_category(['contextid' => $context->id]);
|
||||
|
||||
$this->question1data = [
|
||||
'category' => $qcat->id, 'idnumber' => 'q1',
|
||||
'customfield_f1' => 'some text', 'customfield_f2' => 1,
|
||||
'customfield_f3' => $this->testnow, 'customfield_f4' => 2,
|
||||
'customfield_f5_editor' => ['text' => 'test', 'format' => FORMAT_HTML]];
|
||||
|
||||
$this->question2data = [
|
||||
'category' => $qcat->id, 'idnumber' => 'q2',
|
||||
'customfield_f1' => 'some more text', 'customfield_f2' => 0,
|
||||
'customfield_f3' => $this->testnow, 'customfield_f4' => 1,
|
||||
'customfield_f5_editor' => ['text' => 'test text', 'format' => FORMAT_HTML]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a backup of the course.
|
||||
*
|
||||
* @param stdClass $course The course object.
|
||||
* @return string Unique identifier for this backup.
|
||||
*/
|
||||
protected function backup_course(\stdClass $course): string {
|
||||
global $CFG, $USER;
|
||||
|
||||
// Turn off file logging, otherwise it can't delete the file (Windows).
|
||||
$CFG->backup_file_logger_level = backup::LOG_NONE;
|
||||
|
||||
// Do backup with default settings. MODE_IMPORT means it will just
|
||||
// create the directory and not zip it.
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id,
|
||||
backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
|
||||
$USER->id);
|
||||
$backupid = $bc->get_backupid();
|
||||
$bc->execute_plan();
|
||||
$bc->destroy();
|
||||
|
||||
return $backupid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a backup that has been made earlier.
|
||||
*
|
||||
* @param string $backupid The unique identifier of the backup.
|
||||
* @param string $fullname Full name of the new course that is going to be created.
|
||||
* @param string $shortname Short name of the new course that is going to be created.
|
||||
* @param int $categoryid The course category the backup is going to be restored in.
|
||||
* @return int The new course id.
|
||||
*/
|
||||
protected function restore_course(string $backupid, string $fullname, string $shortname, int $categoryid): int {
|
||||
global $CFG, $USER;
|
||||
|
||||
// Turn off file logging, otherwise it can't delete the file (Windows).
|
||||
$CFG->backup_file_logger_level = backup::LOG_NONE;
|
||||
|
||||
// Do restore to new course with default settings.
|
||||
$newcourseid = restore_dbops::create_new_course($fullname, $shortname, $categoryid);
|
||||
$rc = new restore_controller($backupid, $newcourseid,
|
||||
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
|
||||
backup::TARGET_NEW_COURSE);
|
||||
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
|
||||
return $newcourseid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating questions with custom fields.
|
||||
*/
|
||||
public function test_create_question(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->setup_custom_fields();
|
||||
$this->setup_questions();
|
||||
|
||||
// Create 2 questions.
|
||||
$question1 = $this->qgen->create_question('shortanswer', null, $this->question1data);
|
||||
$question2 = $this->qgen->create_question('shortanswer', null, $this->question2data);
|
||||
|
||||
// Explicitly save the custom field data for the questions, like a form would.
|
||||
$customfieldhandler = \qbank_customfields\customfield\question_handler::create();
|
||||
$this->question1data['id'] = $question1->id;
|
||||
$this->question2data['id'] = $question2->id;
|
||||
$customfieldhandler->instance_form_save((object)$this->question1data);
|
||||
$customfieldhandler->instance_form_save((object)$this->question2data);
|
||||
|
||||
// Get the custom field data associated with these question ids.
|
||||
$q1cfdata = $customfieldhandler->export_instance_data_object($question1->id);
|
||||
$q2cfdata = $customfieldhandler->export_instance_data_object($question2->id);
|
||||
|
||||
$this->assertEquals('some text', $q1cfdata->f1);
|
||||
$this->assertEquals('Yes', $q1cfdata->f2);
|
||||
$this->assertEquals(userdate($this->testnow, get_string('strftimedaydatetime')), $q1cfdata->f3);
|
||||
$this->assertEquals('b', $q1cfdata->f4);
|
||||
$this->assertEquals('test', $q1cfdata->f5);
|
||||
|
||||
$this->assertEquals('some more text', $q2cfdata->f1);
|
||||
$this->assertEquals('No', $q2cfdata->f2);
|
||||
$this->assertEquals(userdate($this->testnow, get_string('strftimedaydatetime')), $q2cfdata->f3);
|
||||
$this->assertEquals('a', $q2cfdata->f4);
|
||||
$this->assertEquals('test text', $q2cfdata->f5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deleting questions with custom fields.
|
||||
*/
|
||||
public function test_delete_question(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->setup_custom_fields();
|
||||
$this->setup_questions();
|
||||
|
||||
// Create 2 questions.
|
||||
$question1 = $this->qgen->create_question('shortanswer', null, $this->question1data);
|
||||
$question2 = $this->qgen->create_question('shortanswer', null, $this->question2data);
|
||||
|
||||
// Explicitly save the custom field data for the questions, like a form would.
|
||||
$customfieldhandler = \qbank_customfields\customfield\question_handler::create();
|
||||
$this->question1data['id'] = $question1->id;
|
||||
$this->question2data['id'] = $question2->id;
|
||||
$customfieldhandler->instance_form_save((object)$this->question1data);
|
||||
$customfieldhandler->instance_form_save((object)$this->question2data);
|
||||
|
||||
// Get the custom field data associated with these question ids.
|
||||
$q1cfdata = $customfieldhandler->export_instance_data_object($question1->id);
|
||||
$q2cfdata = $customfieldhandler->export_instance_data_object($question2->id);
|
||||
|
||||
// Quick check that we have data for the custom fields.
|
||||
$this->assertEquals('some text', $q1cfdata->f1);
|
||||
$this->assertEquals('some more text', $q2cfdata->f1);
|
||||
|
||||
// Delete the questions.
|
||||
question_delete_question($question1->id);
|
||||
question_delete_question($question2->id);
|
||||
|
||||
// Check the custom field data for the questions has also gone.
|
||||
$q1cfdata = $customfieldhandler->export_instance_data_object($question1->id);
|
||||
$q2cfdata = $customfieldhandler->export_instance_data_object($question2->id);
|
||||
|
||||
$this->assertEmpty($q1cfdata->f1);
|
||||
$this->assertEmpty($q2cfdata->f1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom fields attached to questions persist
|
||||
* across the backup and restore process.
|
||||
*/
|
||||
public function test_backup_restore(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->setup_custom_fields();
|
||||
$this->setup_questions();
|
||||
|
||||
$courseshortname = $this->course->shortname;
|
||||
$coursefullname = $this->course->fullname;
|
||||
|
||||
// Create 2 questions.
|
||||
$question1 = $this->qgen->create_question('shortanswer', null, $this->question1data);
|
||||
$question2 = $this->qgen->create_question('shortanswer', null, $this->question2data);
|
||||
|
||||
// Explicitly save the custom field data for the questions, like a form would.
|
||||
$customfieldhandler = \qbank_customfields\customfield\question_handler::create();
|
||||
$this->question1data['id'] = $question1->id;
|
||||
$this->question2data['id'] = $question2->id;
|
||||
$customfieldhandler->instance_form_save((object)$this->question1data);
|
||||
$customfieldhandler->instance_form_save((object)$this->question2data);
|
||||
|
||||
// Create a quiz and the questions to that.
|
||||
$quiz = $this->getDataGenerator()->create_module(
|
||||
'quiz', ['course' => $this->course->id, 'name' => 'restored_quiz']);
|
||||
quiz_add_quiz_question($question1->id, $quiz);
|
||||
quiz_add_quiz_question($question2->id, $quiz);
|
||||
|
||||
// Backup the course.
|
||||
$backupid = $this->backup_course($this->course);
|
||||
|
||||
// Now delete everything.
|
||||
delete_course($this->course, false);
|
||||
question_delete_question($question1->id);
|
||||
question_delete_question($question2->id);
|
||||
|
||||
// Check the custom field data for the questions has also gone.
|
||||
$q1cfdata = $customfieldhandler->export_instance_data_object($question1->id);
|
||||
$q2cfdata = $customfieldhandler->export_instance_data_object($question2->id);
|
||||
|
||||
$this->assertEmpty($q1cfdata->f1);
|
||||
$this->assertEmpty($q2cfdata->f1);
|
||||
|
||||
// Restore the backup we had made earlier into a new course.
|
||||
$newcategory = $this->getDataGenerator()->create_category();
|
||||
$this->restore_course($backupid, $coursefullname, $courseshortname . '_2', $newcategory->id);
|
||||
|
||||
// The questions and their associated custom fields should have been restored.
|
||||
$newquestion1 = $DB->get_record('question', ['idnumber' => 'q1']);
|
||||
$newquestion1cfdata = $customfieldhandler->export_instance_data_object($newquestion1->id);
|
||||
$this->assertEquals('some text', $newquestion1cfdata->f1);
|
||||
$this->assertEquals('Yes', $newquestion1cfdata->f2);
|
||||
$this->assertEquals(userdate($this->testnow, get_string('strftimedaydatetime')), $newquestion1cfdata->f3);
|
||||
$this->assertEquals('b', $newquestion1cfdata->f4);
|
||||
$this->assertEquals('test', $newquestion1cfdata->f5);
|
||||
|
||||
$newquestion2 = $DB->get_record('question', ['idnumber' => 'q2']);
|
||||
$newquestion2cfdata = $customfieldhandler->export_instance_data_object($newquestion2->id);
|
||||
$this->assertEquals('some more text', $newquestion2cfdata->f1);
|
||||
$this->assertEquals('No', $newquestion2cfdata->f2);
|
||||
$this->assertEquals(userdate($this->testnow, get_string('strftimedaydatetime')), $newquestion2cfdata->f3);
|
||||
$this->assertEquals('a', $newquestion2cfdata->f4);
|
||||
$this->assertEquals('test text', $newquestion2cfdata->f5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?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 qbank_customfields_question_handler_testcase
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qbank_customfields_question_handler_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Question setup helper method.
|
||||
*
|
||||
* @return int The question id.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
protected function setup_question(): int {
|
||||
$category = $this->getDataGenerator()->create_category();
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$questioncategory = $questiongenerator->create_question_category(['contextid' => $context->id]);
|
||||
$questiondata = ['category' => $questioncategory->id, 'idnumber' => 'q1'];
|
||||
$question = $questiongenerator->create_question('shortanswer', null, $questiondata);
|
||||
|
||||
return $question->id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom field data.
|
||||
*/
|
||||
public function test_get_field_data(): void {
|
||||
$this->resetAfterTest();
|
||||
$fieldgenerator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
|
||||
$instanceid = $this->setup_question();
|
||||
$fieldvalue = 'test field text';
|
||||
|
||||
$categorydata = new \stdClass();
|
||||
$categorydata->component = 'qbank_customfields';
|
||||
$categorydata->area = 'question';
|
||||
$categorydata->name = 'test category';
|
||||
|
||||
$customfieldcatid = $fieldgenerator->create_category($categorydata)->get('id');
|
||||
$field = $fieldgenerator->create_field(['categoryid' => $customfieldcatid, 'type' => 'text', 'shortname' => 'f1']);
|
||||
$fieldgenerator->add_instance_data($field, $instanceid, $fieldvalue);
|
||||
|
||||
// Get the field data.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$fieldinstancedata = $customfieldhandler->get_field_data($field, $instanceid);
|
||||
|
||||
$this->assertEquals($categorydata->name, $fieldinstancedata->get_field()->get_category()->get('name'));
|
||||
$this->assertEquals($fieldvalue, $fieldinstancedata->get_value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting custom field data for table display.
|
||||
*/
|
||||
public function test_display_custom_field_table(): void {
|
||||
$this->resetAfterTest();
|
||||
$fieldgenerator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
|
||||
$instanceid = $this->setup_question();
|
||||
$fieldvalue = 'test field text';
|
||||
|
||||
$categorydata = new \stdClass();
|
||||
$categorydata->component = 'qbank_customfields';
|
||||
$categorydata->area = 'question';
|
||||
$categorydata->name = 'test category';
|
||||
|
||||
$customfieldcatid = $fieldgenerator->create_category($categorydata)->get('id');
|
||||
$field = $fieldgenerator->create_field(['categoryid' => $customfieldcatid, 'type' => 'text', 'shortname' => 'f1']);
|
||||
$fieldgenerator->add_instance_data($field, $instanceid, $fieldvalue);
|
||||
|
||||
// Get the field data.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$fieldinstancedata = $customfieldhandler->get_field_data($field, $instanceid);
|
||||
$output = $customfieldhandler->display_custom_field_table($fieldinstancedata);
|
||||
|
||||
$this->assertStringContainsString($fieldvalue, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting categories and field data for a specific instance.
|
||||
*/
|
||||
public function test_get_categories_fields_data(): void {
|
||||
$this->resetAfterTest();
|
||||
$fieldgenerator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
|
||||
|
||||
$instanceid = $this->setup_question();
|
||||
$field1value = 'first field text';
|
||||
$field2value = 'second field text';
|
||||
$field3value = 'third field text';
|
||||
|
||||
$categorydata = new \stdClass();
|
||||
$categorydata->component = 'qbank_customfields';
|
||||
$categorydata->area = 'question';
|
||||
$categorydata->name = 'test category';
|
||||
|
||||
$customfieldcat1id = $fieldgenerator->create_category($categorydata)->get('id');
|
||||
$categorydata->name = 'test category two';
|
||||
$customfieldcat2id = $fieldgenerator->create_category($categorydata)->get('id');
|
||||
|
||||
$field1 = $fieldgenerator->create_field(['categoryid' => $customfieldcat1id, 'type' => 'text', 'shortname' => 'f1']);
|
||||
$fieldgenerator->add_instance_data($field1, $instanceid, $field1value);
|
||||
$field2 = $fieldgenerator->create_field(['categoryid' => $customfieldcat1id, 'type' => 'text', 'shortname' => 'f2']);
|
||||
$fieldgenerator->add_instance_data($field2, $instanceid, $field2value);
|
||||
|
||||
$field3 = $fieldgenerator->create_field(['categoryid' => $customfieldcat2id, 'type' => 'text', 'shortname' => 'f3']);
|
||||
$fieldgenerator->add_instance_data($field3, $instanceid, $field3value);
|
||||
|
||||
// Get the field data.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$outputdata = $customfieldhandler->get_categories_fields_data($instanceid);
|
||||
|
||||
$this->assertEquals($field1value, $outputdata['test category'][0]['value']);
|
||||
$this->assertEquals($field2value, $outputdata['test category'][1]['value']);
|
||||
$this->assertEquals($field3value, $outputdata['test category two'][0]['value']);
|
||||
|
||||
// While we're here, lets test the rendering of this data.
|
||||
$outputhtml = $customfieldhandler->display_custom_categories_fields($outputdata);
|
||||
|
||||
$this->assertStringContainsString($field1value, $outputhtml);
|
||||
$this->assertStringContainsString($field2value, $outputhtml);
|
||||
$this->assertStringContainsString($field3value, $outputhtml);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Version information for qbank_customfields.
|
||||
*
|
||||
* @package qbank_customfields
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'qbank_customfields';
|
||||
$plugin->version = 2021100500;
|
||||
$plugin->requires = 2021052500;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
@@ -219,6 +219,10 @@ if ($cm !== null) {
|
||||
|
||||
$toform->inpopup = $inpopup;
|
||||
|
||||
// Prepare custom fields data.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$customfieldhandler->instance_form_before_set_data($toform);
|
||||
|
||||
$mform->set_data($toform);
|
||||
|
||||
if ($mform->is_cancelled()) {
|
||||
@@ -281,6 +285,9 @@ if ($mform->is_cancelled()) {
|
||||
context_course::instance($fromform->courseid), $fromform->coursetags, 0);
|
||||
}
|
||||
|
||||
// Update custom fields if there are any of them in the form.
|
||||
$customfieldhandler->instance_form_save($fromform);
|
||||
|
||||
// Purge this question from the cache.
|
||||
question_bank::notify_question_edited($question->id);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class viewquestionname_column_helper extends column_base {
|
||||
|
||||
protected function label_for($question): string {
|
||||
if (is_null($this->checkboxespresent)) {
|
||||
$this->checkboxespresent = $this->qbank->has_column('core_question\local\bank\checkbox_column');
|
||||
$this->checkboxespresent = $this->qbank->has_column('checkbox_column');
|
||||
}
|
||||
if ($this->checkboxespresent) {
|
||||
return 'checkq' . $question->id;
|
||||
|
||||
@@ -232,6 +232,7 @@ abstract class column_base {
|
||||
protected function get_classes(): string {
|
||||
$classes = $this->get_extra_classes();
|
||||
$classes[] = $this->get_name();
|
||||
$classes[] = 'pr-3';
|
||||
return implode(' ', $classes);
|
||||
}
|
||||
|
||||
@@ -243,6 +244,17 @@ abstract class column_base {
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
/**
|
||||
* Get the name of this column. This must be unique.
|
||||
* When using the inherited class to make many columns from one parent,
|
||||
* ensure each instance returns a unique value.
|
||||
*
|
||||
* @return string The unique name;
|
||||
*/
|
||||
public function get_column_name() {
|
||||
return (new \ReflectionClass($this))->getShortName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Any extra class names you would like applied to every cell in this column.
|
||||
*
|
||||
|
||||
@@ -263,30 +263,30 @@ class view {
|
||||
$plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php');
|
||||
foreach ($plugins as $componentname => $plugin) {
|
||||
$pluginentrypointobject = new $plugin();
|
||||
$pluginobjects = $pluginentrypointobject->get_question_columns($this);
|
||||
$plugincolumnobjects = $pluginentrypointobject->get_question_columns($this);
|
||||
// Don't need the plugins without column objects.
|
||||
if (empty($pluginobjects)) {
|
||||
if (empty($plugincolumnobjects)) {
|
||||
unset($plugins[$componentname]);
|
||||
continue;
|
||||
}
|
||||
foreach ($pluginobjects as $pluginobject) {
|
||||
$classname = new \ReflectionClass(get_class($pluginobject));
|
||||
foreach ($plugincolumnobjects as $columnobject) {
|
||||
$columnname = $columnobject->get_column_name();
|
||||
foreach ($corequestionbankcolumns as $key => $corequestionbankcolumn) {
|
||||
if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) {
|
||||
unset($questionbankclasscolumns[$classname->getShortName()]);
|
||||
unset($questionbankclasscolumns[$columnname]);
|
||||
continue;
|
||||
}
|
||||
// Check if it has custom preference selector to view/hide.
|
||||
if ($pluginobject->has_preference()) {
|
||||
if (!$pluginobject->get_preference()) {
|
||||
if ($columnobject->has_preference()) {
|
||||
if (!$columnobject->get_preference()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ($corequestionbankcolumn === $classname->getShortName()) {
|
||||
$questionbankclasscolumns[$classname->getShortName()] = $pluginobject;
|
||||
if ($corequestionbankcolumn === $columnname) {
|
||||
$questionbankclasscolumns[$columnname] = $columnobject;
|
||||
} else {
|
||||
// Any community plugin for column/action.
|
||||
$newpluginclasscolumns[$classname->getShortName()] = $pluginobject;
|
||||
$newpluginclasscolumns[$columnname] = $columnobject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,7 @@ class view {
|
||||
if (empty($classobject)) {
|
||||
continue;
|
||||
}
|
||||
$this->requiredcolumns[get_class($classobject)] = $classobject;
|
||||
$this->requiredcolumns[$classobject->get_column_name()] = $classobject;
|
||||
}
|
||||
|
||||
return $this->requiredcolumns;
|
||||
@@ -366,9 +366,9 @@ class view {
|
||||
$this->extrarows = [];
|
||||
foreach ($wanted as $column) {
|
||||
if ($column->is_extra_row()) {
|
||||
$this->extrarows[get_class($column)] = $column;
|
||||
$this->extrarows[$column->get_column_name()] = $column;
|
||||
} else {
|
||||
$this->visiblecolumns[get_class($column)] = $column;
|
||||
$this->visiblecolumns[$column->get_column_name()] = $column;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,7 +904,7 @@ class view {
|
||||
protected function create_new_question_form($category, $canadd): void {
|
||||
if (\core\plugininfo\qbank::is_plugin_enabled('qbank_editquestion')) {
|
||||
echo editquestion_helper::create_new_question_button($category->id,
|
||||
$this->requiredcolumns['qbank_editquestion\edit_action_column']->editquestionurl->params(), $canadd);
|
||||
$this->requiredcolumns['edit_action_column']->editquestionurl->params(), $canadd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1115,7 +1115,7 @@ class view {
|
||||
*/
|
||||
protected function start_table() {
|
||||
debugging('Function start_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER);
|
||||
echo '<table id="categoryquestions">' . "\n";
|
||||
echo '<table id="categoryquestions" class="table table-responsive">' . "\n";
|
||||
echo "<thead>\n";
|
||||
$this->print_table_headers();
|
||||
echo "</thead>\n";
|
||||
|
||||
@@ -32,7 +32,8 @@ Feature: The questions in the question bank can be sorted in various ways
|
||||
|
||||
@javascript
|
||||
Scenario: The questions can be sorted by idnumber
|
||||
When I follow "Sort by ID number ascending"
|
||||
When I change the window size to "large"
|
||||
And I follow "Sort by ID number ascending"
|
||||
Then "C question 3 name" "checkbox" should appear after "A question 1 name" "checkbox"
|
||||
And I should see "numidnum</c" in the "C question 3 name" "table_row"
|
||||
And I follow "Sort by ID number descending"
|
||||
|
||||
@@ -222,6 +222,11 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$this->add_tag_fields($mform);
|
||||
}
|
||||
|
||||
// Add custom fields to the form.
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$customfieldhandler->set_parent_context($this->categorycontext); // For question handler only.
|
||||
$customfieldhandler->instance_form_definition($mform, empty($this->question->id) ? 0 : $this->question->id);
|
||||
|
||||
if (!empty($this->question->id)) {
|
||||
$mform->addElement('header', 'createdmodifiedheader',
|
||||
get_string('createdmodifiedheader', 'question'));
|
||||
@@ -285,6 +290,16 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
// By default, do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tweak the form with values provided by custom fields in use.
|
||||
*/
|
||||
public function definition_after_data() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$customfieldhandler = qbank_customfields\customfield\question_handler::create();
|
||||
$customfieldhandler->instance_form_definition_after_data($mform, empty($this->question->id) ? 0 : $this->question->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the question being edited in a state where it can be previewed?
|
||||
* @return bool whether to show the preview link.
|
||||
@@ -855,6 +870,10 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the custom field validation.
|
||||
$customfieldhandler = core_course\customfield\course_handler::create();
|
||||
$errors = array_merge($errors, $customfieldhandler->instance_form_validation($fromform, $files));
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,11 @@ abstract class question_definition {
|
||||
/** @var array of question_hints. */
|
||||
public $hints = array();
|
||||
|
||||
/**
|
||||
* @var array of array of \core_customfield\data_controller objects indexed by fieldid for the questions custom fields.
|
||||
*/
|
||||
public $customfields = array();
|
||||
|
||||
/**
|
||||
* Constructor. Normally to get a question, you call
|
||||
* {@link question_bank::load_question()}, but questions can be created
|
||||
|
||||
@@ -964,6 +964,9 @@ class question_type {
|
||||
}
|
||||
|
||||
$this->initialise_question_hints($question, $questiondata);
|
||||
|
||||
// Add the custom fields.
|
||||
$this->initialise_custom_fields($question, $questiondata);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -990,6 +993,17 @@ class question_type {
|
||||
return question_hint::load_from_record($hint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise question custom fields.
|
||||
* @param question_definition $question the question_definition we are creating.
|
||||
* @param object $questiondata the question data loaded from the database.
|
||||
*/
|
||||
protected function initialise_custom_fields(question_definition $question, $questiondata) {
|
||||
if (!empty($questiondata->customfields)) {
|
||||
$question->customfields = $questiondata->customfields;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the combined feedback fields.
|
||||
* @param question_definition $question the question_definition we are creating.
|
||||
|
||||
Reference in New Issue
Block a user