MDL-71585 qbank_managecategories: Add managecategories to core
This implementation will introduce a qbank plugin "managecategories" which will add the question categories feature in the question bank view by replacing the core classes. Having this plugin will give users the flexibility of enabling or disabling the category tab.
This commit is contained in:
@@ -60,7 +60,7 @@ abstract class question_base extends base {
|
||||
['courseid' => $this->courseid, 'cat' => $cat, 'lastchanged' => $this->objectid]);
|
||||
}
|
||||
// Lets try viewing from the frontpage for contexts above course.
|
||||
return new \moodle_url('/question/category.php',
|
||||
return new \moodle_url('/question/bank/managecategories/category.php',
|
||||
['courseid' => SITEID, 'edit' => $this->other['categoryid'], 'lastchanged' => $this->objectid]);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ abstract class question_category_base extends base {
|
||||
return new \moodle_url('/question/edit.php', ['courseid' => $this->courseid, 'cat' => $cat]);
|
||||
}
|
||||
// Lets try viewing from the frontpage for contexts above course.
|
||||
return new \moodle_url('/question/category.php', ['courseid' => SITEID, 'edit' => $this->objectid]);
|
||||
return new \moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID, 'edit' => $this->objectid]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,7 +87,7 @@ class question_moved extends question_base {
|
||||
['courseid' => $this->courseid, 'cat' => $cat, 'lastchanged' => $this->objectid]);
|
||||
}
|
||||
// Lets try viewing from the frontpage for contexts above course.
|
||||
return new \moodle_url('/question/category.php',
|
||||
return new \moodle_url('/question/bank/managecategories/category.php',
|
||||
['courseid' => SITEID, 'edit' => $this->other['newcategoryid'], 'lastchanged' => $this->objectid]);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@ class questions_exported extends question_base {
|
||||
}
|
||||
return new \moodle_url('/question/edit.php', ['courseid' => $this->courseid, 'cat' => $cat]);
|
||||
}
|
||||
return new \moodle_url('/question/category.php', ['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
|
||||
return new \moodle_url('/question/bank/managecategories/category.php',
|
||||
['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,7 +83,8 @@ class questions_imported extends question_base {
|
||||
}
|
||||
return new \moodle_url('/question/edit.php', ['courseid' => $this->courseid, 'cat' => $cat]);
|
||||
}
|
||||
return new \moodle_url('/question/category.php', ['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
|
||||
return new \moodle_url('/question/bank/managecategories/category.php',
|
||||
['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1943,6 +1943,7 @@ class core_plugin_manager {
|
||||
'editquestion',
|
||||
'exportquestions',
|
||||
'importquestions',
|
||||
'managecategories',
|
||||
'viewcreator',
|
||||
'viewquestionname',
|
||||
'viewquestiontext',
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
global $CFG;
|
||||
use qbank_managecategories\helper;
|
||||
require_once("$CFG->libdir/form/selectgroups.php");
|
||||
require_once("$CFG->libdir/questionlib.php");
|
||||
|
||||
@@ -59,8 +60,8 @@ class MoodleQuickForm_questioncategory extends MoodleQuickForm_selectgroups {
|
||||
if (is_array($options)) {
|
||||
$this->_options = $options + $this->_options;
|
||||
$this->loadArrayOptGroups(
|
||||
question_category_options($this->_options['contexts'], $this->_options['top'], $this->_options['currentcat'],
|
||||
false, $this->_options['nochildrenof'], false));
|
||||
helper::question_category_options($this->_options['contexts'], $this->_options['top'],
|
||||
$this->_options['currentcat'], false, $this->_options['nochildrenof'], false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-165
@@ -238,18 +238,16 @@ function match_grade_options($gradeoptionsfull, $grade, $matchgrades = 'error')
|
||||
* - random questions
|
||||
*
|
||||
* @param int $categoryid The category ID.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_remove_stale_questions_from_category($categoryid) {
|
||||
global $DB;
|
||||
|
||||
$select = 'category = :categoryid AND (qtype = :qtype OR hidden = :hidden)';
|
||||
$params = ['categoryid' => $categoryid, 'qtype' => 'random', 'hidden' => 1];
|
||||
$questions = $DB->get_recordset_select("question", $select, $params, '', 'id');
|
||||
foreach ($questions as $question) {
|
||||
// The function question_delete_question does not delete questions in use.
|
||||
question_delete_question($question->id);
|
||||
}
|
||||
$questions->close();
|
||||
debugging('Function question_remove_stale_questions_from_category()
|
||||
has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_remove_stale_questions_from_category() instead.',
|
||||
DEBUG_DEVELOPER);
|
||||
\qbank_managecategories\helper::question_remove_stale_questions_from_category($categoryid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1146,27 +1144,14 @@ function sort_categories_by_tree(&$categories, $id = 0, $level = 1) {
|
||||
* @param int $id the category to start the indenting process from.
|
||||
* @param int $depth the indent depth. Used in recursive calls.
|
||||
* @return array a new array of categories, in the right order for the tree.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function flatten_category_tree(&$categories, $id, $depth = 0, $nochildrenof = -1) {
|
||||
|
||||
// Indent the name of this category.
|
||||
$newcategories = array();
|
||||
$newcategories[$id] = $categories[$id];
|
||||
$newcategories[$id]->indentedname = str_repeat(' ', $depth) .
|
||||
$categories[$id]->name;
|
||||
|
||||
// Recursively indent the children.
|
||||
foreach ($categories[$id]->childids as $childid) {
|
||||
if ($childid != $nochildrenof) {
|
||||
$newcategories = $newcategories + flatten_category_tree(
|
||||
$categories, $childid, $depth + 1, $nochildrenof);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the childids array that were temporarily added.
|
||||
unset($newcategories[$id]->childids);
|
||||
|
||||
return $newcategories;
|
||||
debugging('Function flatten_category_tree() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::flatten_category_tree() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::flatten_category_tree($categories, $id, $depth, $nochildrenof);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1174,37 +1159,14 @@ function flatten_category_tree(&$categories, $id, $depth = 0, $nochildrenof = -1
|
||||
*
|
||||
* @param array $categories An array of category objects, for example from the.
|
||||
* @return array The formatted list of categories.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function add_indented_names($categories, $nochildrenof = -1) {
|
||||
|
||||
// Add an array to each category to hold the child category ids. This array
|
||||
// will be removed again by flatten_category_tree(). It should not be used
|
||||
// outside these two functions.
|
||||
foreach (array_keys($categories) as $id) {
|
||||
$categories[$id]->childids = array();
|
||||
}
|
||||
|
||||
// Build the tree structure, and record which categories are top-level.
|
||||
// We have to be careful, because the categories array may include published
|
||||
// categories from other courses, but not their parents.
|
||||
$toplevelcategoryids = array();
|
||||
foreach (array_keys($categories) as $id) {
|
||||
if (!empty($categories[$id]->parent) &&
|
||||
array_key_exists($categories[$id]->parent, $categories)) {
|
||||
$categories[$categories[$id]->parent]->childids[] = $id;
|
||||
} else {
|
||||
$toplevelcategoryids[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten the tree to and add the indents.
|
||||
$newcategories = array();
|
||||
foreach ($toplevelcategoryids as $id) {
|
||||
$newcategories = $newcategories + flatten_category_tree(
|
||||
$categories, $id, 0, $nochildrenof);
|
||||
}
|
||||
|
||||
return $newcategories;
|
||||
debugging('Function add_indented_names() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::add_indented_names() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::add_indented_names($categories, $nochildrenof);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1218,30 +1180,15 @@ function add_indented_names($categories, $nochildrenof = -1) {
|
||||
* @param integer $only_editable if true, exclude categories this user is not allowed to edit.
|
||||
* @param integer $selected optionally, the id of a category to be selected by
|
||||
* default in the dropdown.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_category_select_menu($contexts, $top = false, $currentcat = 0,
|
||||
$selected = "", $nochildrenof = -1) {
|
||||
$categoriesarray = question_category_options($contexts, $top, $currentcat,
|
||||
false, $nochildrenof, false);
|
||||
if ($selected) {
|
||||
$choose = '';
|
||||
} else {
|
||||
$choose = 'choosedots';
|
||||
}
|
||||
$options = array();
|
||||
foreach ($categoriesarray as $group => $opts) {
|
||||
$options[] = array($group => $opts);
|
||||
}
|
||||
echo html_writer::label(get_string('questioncategory', 'core_question'), 'id_movetocategory', false, array('class' => 'accesshide'));
|
||||
$attrs = array(
|
||||
'id' => 'id_movetocategory',
|
||||
'class' => 'custom-select',
|
||||
'data-action' => 'toggle',
|
||||
'data-togglegroup' => 'qbank',
|
||||
'data-toggle' => 'action',
|
||||
'disabled' => true,
|
||||
);
|
||||
echo html_writer::select($options, 'category', $selected, $choose, $attrs);
|
||||
debugging('Function question_category_select_menu() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_category_select_menu() instead.', DEBUG_DEVELOPER);
|
||||
\qbank_managecategories\helper::question_category_select_menu($contexts, $top, $currentcat, $selected, $nochildrenof);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1366,16 +1313,14 @@ function question_make_default_categories($contexts) {
|
||||
* @param string $sortorder used as the ORDER BY clause in the select statement.
|
||||
* @param bool $top Whether to return the top categories or not.
|
||||
* @return array of category objects.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function get_categories_for_contexts($contexts, $sortorder = 'parent, sortorder, name ASC', $top = false) {
|
||||
global $DB;
|
||||
$topwhere = $top ? '' : 'AND c.parent <> 0';
|
||||
return $DB->get_records_sql("
|
||||
SELECT c.*, (SELECT count(1) FROM {question} q
|
||||
WHERE c.id = q.category AND q.hidden='0' AND q.parent='0') AS questioncount
|
||||
FROM {question_categories} c
|
||||
WHERE c.contextid IN ($contexts) $topwhere
|
||||
ORDER BY $sortorder");
|
||||
debugging('Function get_categories_for_contexts() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::get_categories_for_contexts() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::get_categories_for_contexts($contexts, $sortorder, $top);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1388,81 +1333,27 @@ function get_categories_for_contexts($contexts, $sortorder = 'parent, sortorder,
|
||||
* @param int $nochildrenof
|
||||
* @param boolean $escapecontextnames Whether the returned name of the thing is to be HTML escaped or not.
|
||||
* @return array
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_category_options($contexts, $top = false, $currentcat = 0,
|
||||
$popupform = false, $nochildrenof = -1, $escapecontextnames = true) {
|
||||
global $CFG;
|
||||
$pcontexts = array();
|
||||
foreach ($contexts as $context) {
|
||||
$pcontexts[] = $context->id;
|
||||
}
|
||||
$contextslist = join(', ', $pcontexts);
|
||||
|
||||
$categories = get_categories_for_contexts($contextslist, 'parent, sortorder, name ASC', $top);
|
||||
|
||||
if ($top) {
|
||||
$categories = question_fix_top_names($categories);
|
||||
}
|
||||
|
||||
$categories = question_add_context_in_key($categories);
|
||||
$categories = add_indented_names($categories, $nochildrenof);
|
||||
|
||||
// sort cats out into different contexts
|
||||
$categoriesarray = array();
|
||||
foreach ($pcontexts as $contextid) {
|
||||
$context = context::instance_by_id($contextid);
|
||||
$contextstring = $context->get_context_name(true, true, $escapecontextnames);
|
||||
foreach ($categories as $category) {
|
||||
if ($category->contextid == $contextid) {
|
||||
$cid = $category->id;
|
||||
if ($currentcat != $cid || $currentcat == 0) {
|
||||
$a = new stdClass;
|
||||
$a->name = format_string($category->indentedname, true,
|
||||
array('context' => $context));
|
||||
if ($category->idnumber !== null && $category->idnumber !== '') {
|
||||
$a->idnumber = s($category->idnumber);
|
||||
}
|
||||
if (!empty($category->questioncount)) {
|
||||
$a->questioncount = $category->questioncount;
|
||||
}
|
||||
if (isset($a->idnumber) && isset($a->questioncount)) {
|
||||
$formattedname = get_string('categorynamewithidnumberandcount', 'question', $a);
|
||||
} else if (isset($a->idnumber)) {
|
||||
$formattedname = get_string('categorynamewithidnumber', 'question', $a);
|
||||
} else if (isset($a->questioncount)) {
|
||||
$formattedname = get_string('categorynamewithcount', 'question', $a);
|
||||
} else {
|
||||
$formattedname = $a->name;
|
||||
}
|
||||
$categoriesarray[$contextstring][$cid] = $formattedname;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($popupform) {
|
||||
$popupcats = array();
|
||||
foreach ($categoriesarray as $contextstring => $optgroup) {
|
||||
$group = array();
|
||||
foreach ($optgroup as $key => $value) {
|
||||
$key = str_replace($CFG->wwwroot, '', $key);
|
||||
$group[$key] = $value;
|
||||
}
|
||||
$popupcats[] = array($contextstring => $group);
|
||||
}
|
||||
return $popupcats;
|
||||
} else {
|
||||
return $categoriesarray;
|
||||
}
|
||||
debugging('Function question_category_options() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_category_options() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::question_category_options($contexts, $top, $currentcat,
|
||||
$popupform, $nochildrenof, $escapecontextnames);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_add_context_in_key($categories) {
|
||||
$newcatarray = array();
|
||||
foreach ($categories as $id => $category) {
|
||||
$category->parent = "$category->parent,$category->contextid";
|
||||
$category->id = "$category->id,$category->contextid";
|
||||
$newcatarray["$id,$category->contextid"] = $category;
|
||||
}
|
||||
return $newcatarray;
|
||||
debugging('Function question_add_context_in_key() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_add_context_in_key() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::question_add_context_in_key($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1471,17 +1362,14 @@ function question_add_context_in_key($categories) {
|
||||
* @param array $categories An array of question categories.
|
||||
* @param boolean $escape Whether the returned name of the thing is to be HTML escaped or not.
|
||||
* @return array The same question category list given to the function, with the top category names being translated.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_fix_top_names($categories, $escape = true) {
|
||||
|
||||
foreach ($categories as $id => $category) {
|
||||
if ($category->parent == 0) {
|
||||
$context = context::instance_by_id($category->contextid);
|
||||
$categories[$id]->name = get_string('topfor', 'question', $context->get_context_name(false, false, $escape));
|
||||
}
|
||||
}
|
||||
|
||||
return $categories;
|
||||
debugging('Function question_fix_top_names() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_fix_top_names() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::question_fix_top_names($categories, $escape);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -517,68 +517,6 @@ class core_questionlib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(1, $DB->count_records('question_categories', ['contextid' => $qcat->contextid, 'parent' => 0]));
|
||||
}
|
||||
|
||||
public function test_question_remove_stale_questions_from_category() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$course = $dg->create_course();
|
||||
$quiz = $dg->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
$qgen = $dg->get_plugin_generator('core_question');
|
||||
$context = context_system::instance();
|
||||
|
||||
$qcat1 = $qgen->create_question_category(['contextid' => $context->id]);
|
||||
$q1a = $qgen->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]);
|
||||
|
||||
$qcat2 = $qgen->create_question_category(['contextid' => $context->id]);
|
||||
$q2a = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden.
|
||||
$q2b = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]);
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]);
|
||||
quiz_add_quiz_question($q2b->id, $quiz);
|
||||
quiz_add_random_questions($quiz, 0, $qcat2->id, 1, false);
|
||||
|
||||
// We added one random question to the quiz and we expect the quiz to have only one random question.
|
||||
$q2d = $DB->get_record_sql("SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {quiz_slots} s ON s.questionid = q.id
|
||||
WHERE q.qtype = :qtype
|
||||
AND s.quizid = :quizid",
|
||||
array('qtype' => 'random', 'quizid' => $quiz->id), MUST_EXIST);
|
||||
|
||||
// The following 2 lines have to be after the quiz_add_random_questions() call above.
|
||||
// Otherwise, quiz_add_random_questions() will to be "smart" and use them instead of creating a new "random" question.
|
||||
$q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
|
||||
$q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
|
||||
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
|
||||
// Non-existing category, nothing will happen.
|
||||
question_remove_stale_questions_from_category(0);
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
|
||||
// First category, should be empty afterwards.
|
||||
question_remove_stale_questions_from_category($qcat1->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q1a->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q1b->id]));
|
||||
|
||||
// Second category, used questions should be left untouched.
|
||||
question_remove_stale_questions_from_category($qcat2->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q2a->id]));
|
||||
$this->assertTrue($DB->record_exists('question', ['id' => $q2b->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q2c->id]));
|
||||
$this->assertTrue($DB->record_exists('question', ['id' => $q2d->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* get_question_options should add the category object to the given question.
|
||||
*/
|
||||
|
||||
@@ -69,6 +69,17 @@ information provided here is intended especially for developers.
|
||||
rendered. The default icon for "select" types has also changed to a dropdown caret ("t/expanded").
|
||||
* The function message_send() in messagelib.php now returns false if there is an error sending the message to the
|
||||
message processor (MDL-70046).
|
||||
* The following functions are deprecated in questionlib.php and moved to the new location.
|
||||
These are marked for final deprecation on 4.4:
|
||||
- question_remove_stale_questions_from_category() =>
|
||||
qbank_managecategories\helper::question_remove_stale_questions_from_category()
|
||||
- flatten_category_tree() => qbank_managecategories\helper::flatten_category_tree()
|
||||
- add_indented_names() => qbank_managecategories\helper::add_indented_names()
|
||||
- question_category_select_menu() => qbank_managecategories\helper::question_category_select_menu()
|
||||
- get_categories_for_contexts() => qbank_managecategories\helper::get_categories_for_contexts()
|
||||
- question_category_options() => qbank_managecategories\helper::question_category_options()
|
||||
- question_add_context_in_key() => qbank_managecategories\helper::question_add_context_in_key()
|
||||
- question_fix_top_names() => qbank_managecategories\helper::question_fix_top_names()
|
||||
|
||||
=== 3.11.2 ===
|
||||
* For security reasons, filelib has been updated so all requests now use emulated redirects.
|
||||
|
||||
@@ -28,7 +28,8 @@ require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
|
||||
require_once($CFG->dirroot . '/question/editlib.php');
|
||||
require_once($CFG->dirroot . '/question/category_class.php');
|
||||
|
||||
use qbank_managecategories\question_category_object;
|
||||
|
||||
list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
|
||||
question_edit_setup('editq', '/mod/quiz/addrandom.php', true);
|
||||
|
||||
+13
-10
@@ -85,19 +85,22 @@ class quiz_add_random_form extends moodleform {
|
||||
|
||||
$mform->addElement('submit', 'existingcategory', get_string('addrandomquestion', 'quiz'));
|
||||
|
||||
// Random from a new category section.
|
||||
$mform->addElement('header', 'newcategoryheader',
|
||||
get_string('randomquestionusinganewcategory', 'quiz'));
|
||||
// If the manage categories plugins is enabled, add the elements to create a new category in the form.
|
||||
if (\core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME)) {
|
||||
// Random from a new category section.
|
||||
$mform->addElement('header', 'newcategoryheader',
|
||||
get_string('randomquestionusinganewcategory', 'quiz'));
|
||||
|
||||
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
|
||||
$mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
|
||||
array('contexts' => $usablecontexts, 'top' => true));
|
||||
$mform->addHelpButton('parent', 'parentcategory', 'question');
|
||||
$mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
|
||||
array('contexts' => $usablecontexts, 'top' => true));
|
||||
$mform->addHelpButton('parent', 'parentcategory', 'question');
|
||||
|
||||
$mform->addElement('submit', 'newcategory',
|
||||
get_string('createcategoryandaddrandomquestion', 'quiz'));
|
||||
$mform->addElement('submit', 'newcategory',
|
||||
get_string('createcategoryandaddrandomquestion', 'quiz'));
|
||||
}
|
||||
|
||||
// Cancel button.
|
||||
$mform->addElement('cancel');
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("mod_quiz/add_question_modal_launcher",["jquery","core/notification","core/modal_factory"],function(a,b,c){return{init:function init(d,e,f,g){var h=a("body");return c.create({type:d,large:!0,preShowCallback:function preShowCallback(b,c){b=a(b);c.setContextId(f);c.setAddOnPageId(b.attr("data-addonpage"));c.setTitle(b.attr("data-header"));if(g){g(b,c)}}},[h,e]).fail(b.exception)}}});
|
||||
define ("mod_quiz/add_question_modal_launcher",["jquery","core/notification","core/modal_factory"],function(a,b,c){return{init:function init(d,e,f,g){var h=4<arguments.length&&arguments[4]!==void 0?arguments[4]:!0,i=a("body");return c.create({type:d,large:!0,templateContext:{hidden:h},preShowCallback:function preShowCallback(b,c){b=a(b);c.setContextId(f);c.setAddOnPageId(b.attr("data-addonpage"));c.setTitle(b.attr("data-header"));if(g){g(b,c)}}},[i,e]).fail(b.exception)}}});
|
||||
//# sourceMappingURL=add_question_modal_launcher.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../src/add_question_modal_launcher.js"],"names":["define","$","Notification","ModalFactory","init","modalType","selector","contextId","preShowCallback","body","create","type","large","triggerElement","modal","setContextId","setAddOnPageId","attr","setTitle","fail","exception"],"mappings":"AAsBAA,OAAM,wCACF,CACI,QADJ,CAEI,mBAFJ,CAGI,oBAHJ,CADE,CAMF,SACIC,CADJ,CAEIC,CAFJ,CAGIC,CAHJ,CAIE,CAEF,MAAO,CAWHC,IAAI,CAAE,cAASC,CAAT,CAAoBC,CAApB,CAA8BC,CAA9B,CAAyCC,CAAzC,CAA0D,CAC5D,GAAIC,CAAAA,CAAI,CAAGR,CAAC,CAAC,MAAD,CAAZ,CAOA,MAAOE,CAAAA,CAAY,CAACO,MAAb,CACH,CACIC,IAAI,CAAEN,CADV,CAEIO,KAAK,GAFT,CAKIJ,eAAe,CAAE,yBAASK,CAAT,CAAyBC,CAAzB,CAAgC,CAC7CD,CAAc,CAAGZ,CAAC,CAACY,CAAD,CAAlB,CACAC,CAAK,CAACC,YAAN,CAAmBR,CAAnB,EACAO,CAAK,CAACE,cAAN,CAAqBH,CAAc,CAACI,IAAf,CAAoB,gBAApB,CAArB,EACAH,CAAK,CAACI,QAAN,CAAeL,CAAc,CAACI,IAAf,CAAoB,aAApB,CAAf,EAEA,GAAIT,CAAJ,CAAqB,CACjBA,CAAe,CAACK,CAAD,CAAiBC,CAAjB,CAClB,CACJ,CAdL,CADG,CAmBH,CAACL,CAAD,CAAOH,CAAP,CAnBG,EAoBLa,IApBK,CAoBAjB,CAAY,CAACkB,SApBb,CAqBV,CAxCE,CA0CV,CAtDK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Initialise the an add question modal on the quiz page.\n *\n * @module mod_quiz/add_question_modal_launcher\n * @copyright 2018 Ryan Wyllie <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n [\n 'jquery',\n 'core/notification',\n 'core/modal_factory',\n ],\n function(\n $,\n Notification,\n ModalFactory\n ) {\n\n return {\n /**\n * Create a modal using the modal factory and add listeners to launch the\n * modal when clicked.\n *\n * @param {string} modalType Which modal to create\n * @param {string} selector The selectors for the elements that trigger the modal\n * @param {int} contextId The current context id\n * @param {function} preShowCallback A callback to execute before the modal is shown\n * @return {promise} Resolved with the modal\n */\n init: function(modalType, selector, contextId, preShowCallback) {\n var body = $('body');\n\n // Create a question bank modal using the factory.\n // The same modal will be used by all of the add question\n // links that match \"selector\" on the page. The content\n // of the modal will be changed depending on which link is\n // clicked.\n return ModalFactory.create(\n {\n type: modalType,\n large: true,\n // This callback executes before the modal is shown when the\n // trigger element is clicked.\n preShowCallback: function(triggerElement, modal) {\n triggerElement = $(triggerElement);\n modal.setContextId(contextId);\n modal.setAddOnPageId(triggerElement.attr('data-addonpage'));\n modal.setTitle(triggerElement.attr('data-header'));\n\n if (preShowCallback) {\n preShowCallback(triggerElement, modal);\n }\n }\n },\n // Created a deligated listener rather than a single\n // trigger element.\n [body, selector]\n ).fail(Notification.exception);\n }\n };\n});\n"],"file":"add_question_modal_launcher.min.js"}
|
||||
{"version":3,"sources":["../src/add_question_modal_launcher.js"],"names":["define","$","Notification","ModalFactory","init","modalType","selector","contextId","preShowCallback","showNewCategory","body","create","type","large","templateContext","hidden","triggerElement","modal","setContextId","setAddOnPageId","attr","setTitle","fail","exception"],"mappings":"AAsBAA,OAAM,wCACF,CACI,QADJ,CAEI,mBAFJ,CAGI,oBAHJ,CADE,CAMF,SACIC,CADJ,CAEIC,CAFJ,CAGIC,CAHJ,CAIE,CAEF,MAAO,CAYHC,IAAI,CAAE,cAASC,CAAT,CAAoBC,CAApB,CAA8BC,CAA9B,CAAyCC,CAAzC,CAAkF,IAAxBC,CAAAA,CAAwB,2DAChFC,CAAI,CAAGT,CAAC,CAAC,MAAD,CADwE,CAWpF,MAAOE,CAAAA,CAAY,CAACQ,MAAb,CACH,CACIC,IAAI,CAAEP,CADV,CAEIQ,KAAK,GAFT,CAGIC,eAAe,CAbD,CAClBC,MAAM,CAAEN,CADU,CAUlB,CAMID,eAAe,CAAE,yBAASQ,CAAT,CAAyBC,CAAzB,CAAgC,CAC7CD,CAAc,CAAGf,CAAC,CAACe,CAAD,CAAlB,CACAC,CAAK,CAACC,YAAN,CAAmBX,CAAnB,EACAU,CAAK,CAACE,cAAN,CAAqBH,CAAc,CAACI,IAAf,CAAoB,gBAApB,CAArB,EACAH,CAAK,CAACI,QAAN,CAAeL,CAAc,CAACI,IAAf,CAAoB,aAApB,CAAf,EAEA,GAAIZ,CAAJ,CAAqB,CACjBA,CAAe,CAACQ,CAAD,CAAiBC,CAAjB,CAClB,CACJ,CAfL,CADG,CAoBH,CAACP,CAAD,CAAOJ,CAAP,CApBG,EAqBLgB,IArBK,CAqBApB,CAAY,CAACqB,SArBb,CAsBV,CA7CE,CA+CV,CA3DK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Initialise the an add question modal on the quiz page.\n *\n * @module mod_quiz/add_question_modal_launcher\n * @copyright 2018 Ryan Wyllie <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n [\n 'jquery',\n 'core/notification',\n 'core/modal_factory',\n ],\n function(\n $,\n Notification,\n ModalFactory\n ) {\n\n return {\n /**\n * Create a modal using the modal factory and add listeners to launch the\n * modal when clicked.\n *\n * @param {string} modalType Which modal to create\n * @param {string} selector The selectors for the elements that trigger the modal\n * @param {int} contextId The current context id\n * @param {function} preShowCallback A callback to execute before the modal is shown\n * @param {boolean} showNewCategory Display the New category tab when selecting random questions.\n * @return {promise} Resolved with the modal\n */\n init: function(modalType, selector, contextId, preShowCallback, showNewCategory = true) {\n var body = $('body');\n let templateContext = {\n hidden: showNewCategory,\n };\n\n // Create a question bank modal using the factory.\n // The same modal will be used by all of the add question\n // links that match \"selector\" on the page. The content\n // of the modal will be changed depending on which link is\n // clicked.\n return ModalFactory.create(\n {\n type: modalType,\n large: true,\n templateContext: templateContext,\n // This callback executes before the modal is shown when the\n // trigger element is clicked.\n preShowCallback: function(triggerElement, modal) {\n triggerElement = $(triggerElement);\n modal.setContextId(contextId);\n modal.setAddOnPageId(triggerElement.attr('data-addonpage'));\n modal.setTitle(triggerElement.attr('data-header'));\n\n if (preShowCallback) {\n preShowCallback(triggerElement, modal);\n }\n }\n },\n // Created a deligated listener rather than a single\n // trigger element.\n [body, selector]\n ).fail(Notification.exception);\n }\n };\n});\n"],"file":"add_question_modal_launcher.min.js"}
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("mod_quiz/add_random_question",["mod_quiz/add_question_modal_launcher","mod_quiz/modal_add_random_question"],function(a,b){return{init:function init(c,d,e,f){a.init(b.TYPE,".menu [data-action=\"addarandomquestion\"]",c,function(a,b){b.setCategory(d);b.setReturnUrl(e);b.setCMID(f)})}}});
|
||||
define ("mod_quiz/add_random_question",["mod_quiz/add_question_modal_launcher","mod_quiz/modal_add_random_question"],function(a,b){return{init:function init(c,d,e,f){var g=4<arguments.length&&arguments[4]!==void 0?arguments[4]:!0;a.init(b.TYPE,".menu [data-action=\"addarandomquestion\"]",c,function(a,b){b.setCategory(d);b.setReturnUrl(e);b.setCMID(f)},g)}}});
|
||||
//# sourceMappingURL=add_random_question.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../src/add_random_question.js"],"names":["define","AddQuestionModalLauncher","ModalAddRandomQuestion","init","contextId","category","returnUrl","cmid","TYPE","triggerElement","modal","setCategory","setReturnUrl","setCMID"],"mappings":"AAsBAA,OAAM,gCACF,CACI,sCADJ,CAEI,oCAFJ,CADE,CAKF,SACIC,CADJ,CAEIC,CAFJ,CAGE,CAEF,MAAO,CASHC,IAAI,CAAE,cAASC,CAAT,CAAoBC,CAApB,CAA8BC,CAA9B,CAAyCC,CAAzC,CAA+C,CACjDN,CAAwB,CAACE,IAAzB,CACID,CAAsB,CAACM,IAD3B,CAEI,4CAFJ,CAGIJ,CAHJ,CAKI,SAASK,CAAT,CAAyBC,CAAzB,CAAgC,CAC5BA,CAAK,CAACC,WAAN,CAAkBN,CAAlB,EACAK,CAAK,CAACE,YAAN,CAAmBN,CAAnB,EACAI,CAAK,CAACG,OAAN,CAAcN,CAAd,CACH,CATL,CAWH,CArBE,CAuBV,CAjCK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Initialise the add random question modal on the quiz page.\n *\n * @module mod_quiz/add_random_question\n * @copyright 2018 Ryan Wyllie <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n [\n 'mod_quiz/add_question_modal_launcher',\n 'mod_quiz/modal_add_random_question'\n ],\n function(\n AddQuestionModalLauncher,\n ModalAddRandomQuestion\n ) {\n\n return {\n /**\n * Create the add random question modal.\n *\n * @param {int} contextId Current context id.\n * @param {string} category Category id and category context id comma separated.\n * @param {string} returnUrl URL to return to after form submission.\n * @param {int} cmid Current course module id.\n */\n init: function(contextId, category, returnUrl, cmid) {\n AddQuestionModalLauncher.init(\n ModalAddRandomQuestion.TYPE,\n '.menu [data-action=\"addarandomquestion\"]',\n contextId,\n // Additional values that should be set before the modal is shown.\n function(triggerElement, modal) {\n modal.setCategory(category);\n modal.setReturnUrl(returnUrl);\n modal.setCMID(cmid);\n }\n );\n }\n };\n});\n"],"file":"add_random_question.min.js"}
|
||||
{"version":3,"sources":["../src/add_random_question.js"],"names":["define","AddQuestionModalLauncher","ModalAddRandomQuestion","init","contextId","category","returnUrl","cmid","showNewCategory","TYPE","triggerElement","modal","setCategory","setReturnUrl","setCMID"],"mappings":"AAsBAA,OAAM,gCACF,CACI,sCADJ,CAEI,oCAFJ,CADE,CAKF,SACIC,CADJ,CAEIC,CAFJ,CAGE,CAEF,MAAO,CAUHC,IAAI,CAAE,cAASC,CAAT,CAAoBC,CAApB,CAA8BC,CAA9B,CAAyCC,CAAzC,CAAuE,IAAxBC,CAAAA,CAAwB,2DACzEP,CAAwB,CAACE,IAAzB,CACID,CAAsB,CAACO,IAD3B,CAEI,4CAFJ,CAGIL,CAHJ,CAKI,SAASM,CAAT,CAAyBC,CAAzB,CAAgC,CAC5BA,CAAK,CAACC,WAAN,CAAkBP,CAAlB,EACAM,CAAK,CAACE,YAAN,CAAmBP,CAAnB,EACAK,CAAK,CAACG,OAAN,CAAcP,CAAd,CACH,CATL,CAUIC,CAVJ,CAYH,CAvBE,CAyBV,CAnCK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Initialise the add random question modal on the quiz page.\n *\n * @module mod_quiz/add_random_question\n * @copyright 2018 Ryan Wyllie <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n [\n 'mod_quiz/add_question_modal_launcher',\n 'mod_quiz/modal_add_random_question'\n ],\n function(\n AddQuestionModalLauncher,\n ModalAddRandomQuestion\n ) {\n\n return {\n /**\n * Create the add random question modal.\n *\n * @param {int} contextId Current context id.\n * @param {string} category Category id and category context id comma separated.\n * @param {string} returnUrl URL to return to after form submission.\n * @param {int} cmid Current course module id.\n * @param {boolean} showNewCategory Display the New category tab when selecting random questions.\n */\n init: function(contextId, category, returnUrl, cmid, showNewCategory = true) {\n AddQuestionModalLauncher.init(\n ModalAddRandomQuestion.TYPE,\n '.menu [data-action=\"addarandomquestion\"]',\n contextId,\n // Additional values that should be set before the modal is shown.\n function(triggerElement, modal) {\n modal.setCategory(category);\n modal.setReturnUrl(returnUrl);\n modal.setCMID(cmid);\n },\n showNewCategory\n );\n }\n };\n});\n"],"file":"add_random_question.min.js"}
|
||||
@@ -41,10 +41,14 @@ define(
|
||||
* @param {string} selector The selectors for the elements that trigger the modal
|
||||
* @param {int} contextId The current context id
|
||||
* @param {function} preShowCallback A callback to execute before the modal is shown
|
||||
* @param {boolean} showNewCategory Display the New category tab when selecting random questions.
|
||||
* @return {promise} Resolved with the modal
|
||||
*/
|
||||
init: function(modalType, selector, contextId, preShowCallback) {
|
||||
init: function(modalType, selector, contextId, preShowCallback, showNewCategory = true) {
|
||||
var body = $('body');
|
||||
let templateContext = {
|
||||
hidden: showNewCategory,
|
||||
};
|
||||
|
||||
// Create a question bank modal using the factory.
|
||||
// The same modal will be used by all of the add question
|
||||
@@ -55,6 +59,7 @@ define(
|
||||
{
|
||||
type: modalType,
|
||||
large: true,
|
||||
templateContext: templateContext,
|
||||
// This callback executes before the modal is shown when the
|
||||
// trigger element is clicked.
|
||||
preShowCallback: function(triggerElement, modal) {
|
||||
|
||||
@@ -38,8 +38,9 @@ define(
|
||||
* @param {string} category Category id and category context id comma separated.
|
||||
* @param {string} returnUrl URL to return to after form submission.
|
||||
* @param {int} cmid Current course module id.
|
||||
* @param {boolean} showNewCategory Display the New category tab when selecting random questions.
|
||||
*/
|
||||
init: function(contextId, category, returnUrl, cmid) {
|
||||
init: function(contextId, category, returnUrl, cmid, showNewCategory = true) {
|
||||
AddQuestionModalLauncher.init(
|
||||
ModalAddRandomQuestion.TYPE,
|
||||
'.menu [data-action="addarandomquestion"]',
|
||||
@@ -49,7 +50,8 @@ define(
|
||||
modal.setCategory(category);
|
||||
modal.setReturnUrl(returnUrl);
|
||||
modal.setCMID(cmid);
|
||||
}
|
||||
},
|
||||
showNewCategory
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,7 +119,8 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
$thiscontext->id,
|
||||
$pagevars['cat'],
|
||||
$pageurl->out_as_local_url(true),
|
||||
$pageurl->param('cmid')
|
||||
$pageurl->param('cmid'),
|
||||
\core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME),
|
||||
]);
|
||||
|
||||
// Include the question chooser.
|
||||
|
||||
@@ -45,7 +45,6 @@ require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
|
||||
require_once($CFG->dirroot . '/question/editlib.php');
|
||||
require_once($CFG->dirroot . '/question/category_class.php');
|
||||
|
||||
// These params are only passed from page request to request while we stay on
|
||||
// this page otherwise they would go in question_edit_setup.
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
{{#str}} existingcategory, mod_quiz {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
{{#hidden}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
data-toggle="tab"
|
||||
@@ -55,6 +56,7 @@
|
||||
{{#str}} newcategory, mod_quiz {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
{{/hidden}}
|
||||
</ul>
|
||||
<div class="tab-content" data-region="tab-content">
|
||||
<div class="text-sm-center pt-5" data-region="loading-container">
|
||||
@@ -65,11 +67,13 @@
|
||||
role="tabpanel"
|
||||
data-region="existing-category-container">
|
||||
</div>
|
||||
{{#hidden}}
|
||||
<div class="tab-pane pt-3"
|
||||
id="new-category-{{uniqid}}"
|
||||
role="tabpanel"
|
||||
data-region="new-category-container">
|
||||
</div>
|
||||
{{/hidden}}
|
||||
</div>
|
||||
{{/body}}
|
||||
{{/ core/modal }}
|
||||
|
||||
@@ -17,19 +17,24 @@
|
||||
/**
|
||||
* This script allows a teacher to create, edit and delete question categories.
|
||||
*
|
||||
* @package moodlecore
|
||||
* @subpackage questionbank
|
||||
* @package qbank_managecategories
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @author 2021, Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
require_once("../config.php");
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot."/question/editlib.php");
|
||||
require_once($CFG->dirroot."/question/category_class.php");
|
||||
|
||||
use qbank_managecategories\form\question_move_form;
|
||||
use qbank_managecategories\helper;
|
||||
use qbank_managecategories\question_category_object;
|
||||
|
||||
require_login();
|
||||
core_question\local\bank\helper::require_plugin_enabled(helper::PLUGINNAME);
|
||||
|
||||
list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) =
|
||||
question_edit_setup('categories', '/question/category.php');
|
||||
question_edit_setup('categories', '/question/bank/managecategories/category.php');
|
||||
|
||||
// Get values from form for actions on this page.
|
||||
$param = new stdClass();
|
||||
@@ -48,7 +53,7 @@ $param->moveto = optional_param('moveto', 0, PARAM_INT);
|
||||
$param->edit = optional_param('edit', 0, PARAM_INT);
|
||||
|
||||
$url = new moodle_url($thispageurl);
|
||||
foreach ((array)$param as $key=>$value) {
|
||||
foreach ((array)$param as $key => $value) {
|
||||
if (($key !== 'cancel' && $value !== 0) || ($key === 'cancel' && $value !== '')) {
|
||||
$url->param($key, $value);
|
||||
}
|
||||
@@ -78,9 +83,9 @@ if ($param->moveupcontext || $param->movedowncontext) {
|
||||
}
|
||||
$newtopcat = question_get_top_category($param->tocontext);
|
||||
if (!$newtopcat) {
|
||||
print_error('invalidcontext');
|
||||
throw new moodle_exception('invalidcontext');
|
||||
}
|
||||
$oldcat = $DB->get_record('question_categories', array('id' => $catid), '*', MUST_EXIST);
|
||||
$oldcat = $DB->get_record('question_categories', ['id' => $catid], '*', MUST_EXIST);
|
||||
// Log the move to another context.
|
||||
$category = new stdClass();
|
||||
$category->id = explode(',', $pagevars['cat'], -1)[0];
|
||||
@@ -92,18 +97,18 @@ if ($param->moveupcontext || $param->movedowncontext) {
|
||||
}
|
||||
|
||||
if ($param->delete) {
|
||||
if (!$category = $DB->get_record("question_categories", array("id" => $param->delete))) {
|
||||
print_error('nocate', 'question', $thispageurl->out(), $param->delete);
|
||||
if (!$category = $DB->get_record("question_categories", ["id" => $param->delete])) {
|
||||
throw new moodle_exception('nocate', 'question', $thispageurl->out(), $param->delete);
|
||||
}
|
||||
|
||||
question_remove_stale_questions_from_category($param->delete);
|
||||
$questionstomove = $DB->count_records("question", array("category" => $param->delete));
|
||||
helper::question_remove_stale_questions_from_category($param->delete);
|
||||
$questionstomove = $DB->count_records("question", ["category" => $param->delete]);
|
||||
|
||||
// Second pass, if we still have questions to move, setup the form.
|
||||
if ($questionstomove) {
|
||||
$categorycontext = context::instance_by_id($category->contextid);
|
||||
$qcobject->moveform = new question_move_form($thispageurl,
|
||||
array('contexts' => array($categorycontext), 'currentcat' => $param->delete));
|
||||
['contexts' => [$categorycontext], 'currentcat' => $param->delete]);
|
||||
if ($qcobject->moveform->is_cancelled()) {
|
||||
redirect($thispageurl);
|
||||
} else if ($formdata = $qcobject->moveform->get_data()) {
|
||||
@@ -122,7 +127,7 @@ if ($qcobject->catform->is_cancelled()) {
|
||||
} else if ($catformdata = $qcobject->catform->get_data()) {
|
||||
$catformdata->infoformat = $catformdata->info['format'];
|
||||
$catformdata->info = $catformdata->info['text'];
|
||||
if (!$catformdata->id) {//new category
|
||||
if (!$catformdata->id) {// New category.
|
||||
$qcobject->add_category($catformdata->parent, $catformdata->name,
|
||||
$catformdata->info, false, $catformdata->infoformat, $catformdata->idnumber);
|
||||
} else {
|
||||
@@ -131,7 +136,7 @@ if ($qcobject->catform->is_cancelled()) {
|
||||
}
|
||||
redirect($thispageurl);
|
||||
} else if ((!empty($param->delete) and (!$questionstomove) and confirm_sesskey())) {
|
||||
$qcobject->delete_category($param->delete);//delete the category now no questions to move
|
||||
$qcobject->delete_category($param->delete);// Delete the category now no questions to move.
|
||||
$thispageurl->remove_params('cat', 'category');
|
||||
redirect($thispageurl);
|
||||
}
|
||||
@@ -151,7 +156,7 @@ echo $renderer->extra_horizontal_navigation();
|
||||
// Display the UI.
|
||||
if (!empty($param->edit)) {
|
||||
$qcobject->edit_single_category($param->edit);
|
||||
} else if ($questionstomove){
|
||||
} else if ($questionstomove) {
|
||||
$qcobject->display_move_form($questionstomove, $category);
|
||||
} else {
|
||||
// Display the user interface.
|
||||
@@ -0,0 +1,127 @@
|
||||
<?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_managecategories\form;
|
||||
|
||||
use moodleform;
|
||||
use qbank_managecategories\helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
|
||||
|
||||
/**
|
||||
* Defines the form for editing question categories.
|
||||
*
|
||||
* Form for editing questions categories (name, description, etc.)
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2007 Jamie Pratt [email protected]
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_category_edit_form extends moodleform {
|
||||
|
||||
/**
|
||||
* Build the form definition.
|
||||
*
|
||||
* This adds all the form fields that the manage categories feature needs.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
protected function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$contexts = $this->_customdata['contexts'];
|
||||
$currentcat = $this->_customdata['currentcat'];
|
||||
|
||||
$mform->addElement('header', 'categoryheader', get_string('addcategory', 'question'));
|
||||
|
||||
$mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'),
|
||||
['contexts' => $contexts, 'top' => true, 'currentcat' => $currentcat, 'nochildrenof' => $currentcat]);
|
||||
$mform->setType('parent', PARAM_SEQUENCE);
|
||||
if (helper::question_is_only_child_of_top_category_in_context($currentcat)) {
|
||||
$mform->hardFreeze('parent');
|
||||
}
|
||||
$mform->addHelpButton('parent', 'parentcategory', 'question');
|
||||
|
||||
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
|
||||
$mform->setDefault('name', '');
|
||||
$mform->addRule('name', get_string('categorynamecantbeblank', 'question'), 'required', null, 'client');
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
|
||||
$mform->addElement('editor', 'info', get_string('categoryinfo', 'question'),
|
||||
['rows' => 10], ['noclean' => 1]);
|
||||
$mform->setDefault('info', '');
|
||||
$mform->setType('info', PARAM_RAW);
|
||||
|
||||
$mform->addElement('text', 'idnumber', get_string('idnumber', 'question'), 'maxlength="100" size="10"');
|
||||
$mform->addHelpButton('idnumber', 'idnumber', 'question');
|
||||
$mform->setType('idnumber', PARAM_RAW);
|
||||
|
||||
$this->add_action_buttons(false, get_string('addcategory', 'question'));
|
||||
|
||||
$mform->addElement('hidden', 'id', 0);
|
||||
$mform->setType('id', PARAM_INT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data method.
|
||||
*
|
||||
* Add additional information to current data.
|
||||
* @param \stdClass|array $current Object or array of default current data.
|
||||
*/
|
||||
public function set_data($current) {
|
||||
if (is_object($current)) {
|
||||
$current = (array) $current;
|
||||
}
|
||||
if (!empty($current['info'])) {
|
||||
$current['info'] = ['text' => $current['info'], 'infoformat' => $current['infoformat']];
|
||||
} else {
|
||||
$current['info'] = ['text' => '', 'infoformat' => FORMAT_HTML];
|
||||
}
|
||||
parent::set_data($current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array the errors that were found
|
||||
* @throws \dml_exception|\coding_exception
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
global $DB;
|
||||
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
// Add field validation check for duplicate idnumber.
|
||||
list($parentid, $contextid) = explode(',', $data['parent']);
|
||||
if (((string) $data['idnumber'] !== '') && !empty($contextid)) {
|
||||
$conditions = 'contextid = ? AND idnumber = ?';
|
||||
$params = [$contextid, $data['idnumber']];
|
||||
if (!empty($data['id'])) {
|
||||
$conditions .= ' AND id <> ?';
|
||||
$params[] = $data['id'];
|
||||
}
|
||||
if ($DB->record_exists_select('question_categories', $conditions, $params)) {
|
||||
$errors['idnumber'] = get_string('idnumbertaken', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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_managecategories\form;
|
||||
|
||||
use moodleform;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
|
||||
|
||||
/**
|
||||
* Form for moving questions between categories.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2008 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_move_form extends moodleform {
|
||||
|
||||
/**
|
||||
* Build the form definition.
|
||||
*
|
||||
* This adds all the form fields that the question move feature needs.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
protected function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$currentcat = $this->_customdata['currentcat'];
|
||||
$contexts = $this->_customdata['contexts'];
|
||||
|
||||
$mform->addElement('questioncategory', 'category', get_string('category', 'question'), compact('contexts', 'currentcat'));
|
||||
|
||||
$this->add_action_buttons(true, get_string('categorymoveto', 'question'));
|
||||
|
||||
$mform->addElement('hidden', 'delete', $currentcat);
|
||||
$mform->setType('delete', PARAM_INT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<?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_managecategories;
|
||||
|
||||
use context;
|
||||
use moodle_exception;
|
||||
use html_writer;
|
||||
|
||||
/**
|
||||
* Class helper contains all the library functions.
|
||||
*
|
||||
* Library functions used by qbank_managecategories.
|
||||
* This code is based on lib/questionlib.php by Martin Dougiamas.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Guillermo Gomez Arias <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Name of this plugin.
|
||||
*/
|
||||
const PLUGINNAME = 'qbank_managecategories';
|
||||
|
||||
/**
|
||||
* Remove stale questions from a category.
|
||||
*
|
||||
* While questions should not be left behind when they are not used any more,
|
||||
* it does happen, maybe via restore, or old logic, or uncovered scenarios. When
|
||||
* this happens, the users are unable to delete the question category unless
|
||||
* they move those stale questions to another one category, but to them the
|
||||
* category is empty as it does not contain anything. The purpose of this function
|
||||
* is to detect the questions that may have gone stale and remove them.
|
||||
*
|
||||
* You will typically use this prior to checking if the category contains questions.
|
||||
*
|
||||
* The stale questions (unused and hidden to the user) handled are:
|
||||
* - hidden questions
|
||||
* - random questions
|
||||
*
|
||||
* @param int $categoryid The category ID.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function question_remove_stale_questions_from_category(int $categoryid): void {
|
||||
global $DB;
|
||||
|
||||
$select = 'category = :categoryid AND (qtype = :qtype OR hidden = :hidden)';
|
||||
$params = ['categoryid' => $categoryid, 'qtype' => 'random', 'hidden' => 1];
|
||||
$questions = $DB->get_recordset_select("question", $select, $params, '', 'id');
|
||||
foreach ($questions as $question) {
|
||||
// The function question_delete_question does not delete questions in use.
|
||||
question_delete_question($question->id);
|
||||
}
|
||||
$questions->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this is the only child of a top category in a context.
|
||||
*
|
||||
* @param int $categoryid a category id.
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function question_is_only_child_of_top_category_in_context(int $categoryid): bool {
|
||||
global $DB;
|
||||
return 1 == $DB->count_records_sql("
|
||||
SELECT count(*)
|
||||
FROM {question_categories} c
|
||||
JOIN {question_categories} p ON c.parent = p.id
|
||||
JOIN {question_categories} s ON s.parent = c.parent
|
||||
WHERE c.id = ? AND p.parent = 0", [$categoryid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the category is a "Top" category (with no parent).
|
||||
*
|
||||
* @param int $categoryid a category id.
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function question_is_top_category(int $categoryid): bool {
|
||||
global $DB;
|
||||
return 0 == $DB->get_field('question_categories', 'parent', ['id' => $categoryid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that this user is allowed to delete this category.
|
||||
*
|
||||
* @param int $todelete a category id.
|
||||
* @throws \required_capability_exception
|
||||
* @throws \dml_exception|moodle_exception
|
||||
*/
|
||||
public static function question_can_delete_cat(int $todelete): void {
|
||||
global $DB;
|
||||
if (self::question_is_top_category($todelete)) {
|
||||
throw new moodle_exception('cannotdeletetopcat', 'question');
|
||||
} else if (self::question_is_only_child_of_top_category_in_context($todelete)) {
|
||||
throw new moodle_exception('cannotdeletecate', 'question');
|
||||
} else {
|
||||
$contextid = $DB->get_field('question_categories', 'contextid', ['id' => $todelete]);
|
||||
require_capability('moodle/question:managecategory', context::instance_by_id($contextid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for the use of add_indented_names().
|
||||
*
|
||||
* Recursively adds an indentedname field to each category, starting with the category
|
||||
* with id $id, and dealing with that category and all its children, and
|
||||
* return a new array, with those categories in the right order.
|
||||
*
|
||||
* @param array $categories an array of categories which has had childids
|
||||
* fields added by flatten_category_tree(). Passed by reference for
|
||||
* performance only. It is not modfied.
|
||||
* @param int $id the category to start the indenting process from.
|
||||
* @param int $depth the indent depth. Used in recursive calls.
|
||||
* @param int $nochildrenof
|
||||
* @return array a new array of categories, in the right order for the tree.
|
||||
*/
|
||||
public static function flatten_category_tree(array &$categories, $id, int $depth = 0, int $nochildrenof = -1): array {
|
||||
|
||||
// Indent the name of this category.
|
||||
$newcategories = [];
|
||||
$newcategories[$id] = $categories[$id];
|
||||
$newcategories[$id]->indentedname = str_repeat(' ', $depth) .
|
||||
$categories[$id]->name;
|
||||
|
||||
// Recursively indent the children.
|
||||
foreach ($categories[$id]->childids as $childid) {
|
||||
if ($childid != $nochildrenof) {
|
||||
$newcategories = $newcategories + self::flatten_category_tree(
|
||||
$categories, $childid, $depth + 1, $nochildrenof);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the childids array that were temporarily added.
|
||||
unset($newcategories[$id]->childids);
|
||||
|
||||
return $newcategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format categories into an indented list reflecting the tree structure.
|
||||
*
|
||||
* @param array $categories An array of category objects, for example from the.
|
||||
* @param int $nochildrenof
|
||||
* @return array The formatted list of categories.
|
||||
*/
|
||||
public static function add_indented_names(array $categories, int $nochildrenof = -1): array {
|
||||
|
||||
// Add an array to each category to hold the child category ids. This array
|
||||
// will be removed again by flatten_category_tree(). It should not be used
|
||||
// outside these two functions.
|
||||
foreach (array_keys($categories) as $id) {
|
||||
$categories[$id]->childids = [];
|
||||
}
|
||||
|
||||
// Build the tree structure, and record which categories are top-level.
|
||||
// We have to be careful, because the categories array may include published
|
||||
// categories from other courses, but not their parents.
|
||||
$toplevelcategoryids = [];
|
||||
foreach (array_keys($categories) as $id) {
|
||||
if (!empty($categories[$id]->parent) &&
|
||||
array_key_exists($categories[$id]->parent, $categories)) {
|
||||
$categories[$categories[$id]->parent]->childids[] = $id;
|
||||
} else {
|
||||
$toplevelcategoryids[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten the tree to and add the indents.
|
||||
$newcategories = [];
|
||||
foreach ($toplevelcategoryids as $id) {
|
||||
$newcategories = $newcategories + self::flatten_category_tree(
|
||||
$categories, $id, 0, $nochildrenof);
|
||||
}
|
||||
|
||||
return $newcategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a select menu of question categories.
|
||||
*
|
||||
* Categories from this course and (optionally) published categories from other courses
|
||||
* are included. Optionally, only categories the current user may edit can be included.
|
||||
*
|
||||
* @param array $contexts
|
||||
* @param bool $top
|
||||
* @param int $currentcat
|
||||
* @param string $selected optionally, the id of a category to be selected by
|
||||
* default in the dropdown.
|
||||
* @param int $nochildrenof
|
||||
* @throws \coding_exception|\dml_exception
|
||||
*/
|
||||
public static function question_category_select_menu(array $contexts, bool $top = false, int $currentcat = 0,
|
||||
string $selected = "", int $nochildrenof = -1): void {
|
||||
$categoriesarray = self::question_category_options($contexts, $top, $currentcat,
|
||||
false, $nochildrenof, false);
|
||||
if ($selected) {
|
||||
$choose = '';
|
||||
} else {
|
||||
$choose = 'choosedots';
|
||||
}
|
||||
$options = [];
|
||||
foreach ($categoriesarray as $group => $opts) {
|
||||
$options[] = [$group => $opts];
|
||||
}
|
||||
echo html_writer::label(get_string('questioncategory', 'core_question'),
|
||||
'id_movetocategory', false, ['class' => 'accesshide']);
|
||||
$attrs = [
|
||||
'id' => 'id_movetocategory',
|
||||
'class' => 'custom-select',
|
||||
'data-action' => 'toggle',
|
||||
'data-togglegroup' => 'qbank',
|
||||
'data-toggle' => 'action',
|
||||
'disabled' => true,
|
||||
];
|
||||
echo html_writer::select($options, 'category', $selected, $choose, $attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the category objects, including a count of the number of questions in that category,
|
||||
* for all the categories in the lists $contexts.
|
||||
*
|
||||
* @param mixed $contexts either a single contextid, or a comma-separated list of context ids.
|
||||
* @param string $sortorder used as the ORDER BY clause in the select statement.
|
||||
* @param bool $top Whether to return the top categories or not.
|
||||
* @return array of category objects.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_categories_for_contexts($contexts, string $sortorder = 'parent, sortorder, name ASC',
|
||||
bool $top = false): array {
|
||||
global $DB;
|
||||
$topwhere = $top ? '' : 'AND c.parent <> 0';
|
||||
return $DB->get_records_sql("
|
||||
SELECT c.*, (SELECT count(1) FROM {question} q
|
||||
WHERE c.id = q.category AND q.hidden='0' AND q.parent='0') AS questioncount
|
||||
FROM {question_categories} c
|
||||
WHERE c.contextid IN ($contexts) $topwhere
|
||||
ORDER BY $sortorder");
|
||||
}
|
||||
|
||||
/**
|
||||
* Output an array of question categories.
|
||||
*
|
||||
* @param array $contexts The list of contexts.
|
||||
* @param bool $top Whether to return the top categories or not.
|
||||
* @param int $currentcat
|
||||
* @param bool $popupform
|
||||
* @param int $nochildrenof
|
||||
* @param bool $escapecontextnames Whether the returned name of the thing is to be HTML escaped or not.
|
||||
* @return array
|
||||
* @throws \coding_exception|\dml_exception
|
||||
*/
|
||||
public static function question_category_options(array $contexts, bool $top = false, int $currentcat = 0,
|
||||
bool $popupform = false, int $nochildrenof = -1,
|
||||
bool $escapecontextnames = true): array {
|
||||
global $CFG;
|
||||
$pcontexts = [];
|
||||
foreach ($contexts as $context) {
|
||||
$pcontexts[] = $context->id;
|
||||
}
|
||||
$contextslist = join(', ', $pcontexts);
|
||||
|
||||
$categories = self::get_categories_for_contexts($contextslist, 'parent, sortorder, name ASC', $top);
|
||||
|
||||
if ($top) {
|
||||
$categories = self::question_fix_top_names($categories);
|
||||
}
|
||||
|
||||
$categories = self::question_add_context_in_key($categories);
|
||||
$categories = self::add_indented_names($categories, $nochildrenof);
|
||||
|
||||
// Sort cats out into different contexts.
|
||||
$categoriesarray = [];
|
||||
foreach ($pcontexts as $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
$contextstring = $context->get_context_name(true, true, $escapecontextnames);
|
||||
foreach ($categories as $category) {
|
||||
if ($category->contextid == $contextid) {
|
||||
$cid = $category->id;
|
||||
if ($currentcat != $cid || $currentcat == 0) {
|
||||
$a = new \stdClass;
|
||||
$a->name = format_string($category->indentedname, true,
|
||||
['context' => $context]);
|
||||
if ($category->idnumber !== null && $category->idnumber !== '') {
|
||||
$a->idnumber = s($category->idnumber);
|
||||
}
|
||||
if (!empty($category->questioncount)) {
|
||||
$a->questioncount = $category->questioncount;
|
||||
}
|
||||
if (isset($a->idnumber) && isset($a->questioncount)) {
|
||||
$formattedname = get_string('categorynamewithidnumberandcount', 'question', $a);
|
||||
} else if (isset($a->idnumber)) {
|
||||
$formattedname = get_string('categorynamewithidnumber', 'question', $a);
|
||||
} else if (isset($a->questioncount)) {
|
||||
$formattedname = get_string('categorynamewithcount', 'question', $a);
|
||||
} else {
|
||||
$formattedname = $a->name;
|
||||
}
|
||||
$categoriesarray[$contextstring][$cid] = $formattedname;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($popupform) {
|
||||
$popupcats = [];
|
||||
foreach ($categoriesarray as $contextstring => $optgroup) {
|
||||
$group = [];
|
||||
foreach ($optgroup as $key => $value) {
|
||||
$key = str_replace($CFG->wwwroot, '', $key);
|
||||
$group[$key] = $value;
|
||||
}
|
||||
$popupcats[] = [$contextstring => $group];
|
||||
}
|
||||
return $popupcats;
|
||||
} else {
|
||||
return $categoriesarray;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add context in categories key.
|
||||
*
|
||||
* @param array $categories The list of categories.
|
||||
* @return array
|
||||
*/
|
||||
public static function question_add_context_in_key(array $categories): array {
|
||||
$newcatarray = [];
|
||||
foreach ($categories as $id => $category) {
|
||||
$category->parent = "$category->parent,$category->contextid";
|
||||
$category->id = "$category->id,$category->contextid";
|
||||
$newcatarray["$id,$category->contextid"] = $category;
|
||||
}
|
||||
return $newcatarray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds top categories in the given categories hierarchy and replace their name with a proper localised string.
|
||||
*
|
||||
* @param array $categories An array of question categories.
|
||||
* @param bool $escape Whether the returned name of the thing is to be HTML escaped or not.
|
||||
* @return array The same question category list given to the function, with the top category names being translated.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function question_fix_top_names(array $categories, bool $escape = true): array {
|
||||
|
||||
foreach ($categories as $id => $category) {
|
||||
if ($category->parent == 0) {
|
||||
$context = \context::instance_by_id($category->contextid);
|
||||
$categories[$id]->name = get_string('topfor', 'question', $context->get_context_name(false, false, $escape));
|
||||
}
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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_managecategories;
|
||||
|
||||
/**
|
||||
* Class navigation.
|
||||
*
|
||||
* Plugin entrypoint for navigation.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Guillermo Gomez Arias <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class navigation extends \core_question\local\bank\navigation_node_base {
|
||||
|
||||
public function get_navigation_title(): string {
|
||||
return get_string('categories', 'question');
|
||||
}
|
||||
|
||||
public function get_navigation_key(): string {
|
||||
return 'categories';
|
||||
}
|
||||
|
||||
public function get_navigation_url(): \moodle_url {
|
||||
return new \moodle_url('/question/bank/managecategories/category.php');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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_managecategories;
|
||||
|
||||
/**
|
||||
* Class plugin_feature.
|
||||
*
|
||||
* Entry point for qbank plugin.
|
||||
* Every qbank plugin must extent this class.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @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
|
||||
*/
|
||||
class plugin_feature extends \core_question\local\bank\plugin_features_base {
|
||||
|
||||
public function get_navigation_node(): ?object {
|
||||
return new navigation();
|
||||
}
|
||||
}
|
||||
@@ -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_managecategories\privacy;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for qbank_managecategories implementing null_provider.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @category privacy
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Guillermo Gomez Arias <[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,135 @@
|
||||
<?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_managecategories;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir. '/listlib.php');
|
||||
|
||||
use stdClass;
|
||||
use moodle_list;
|
||||
|
||||
/**
|
||||
* Class representing a list of question categories.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_category_list extends moodle_list {
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
* @var $table
|
||||
*/
|
||||
public $table = "question_categories";
|
||||
|
||||
/**
|
||||
* List item class name.
|
||||
* @var $listitemclassname
|
||||
*/
|
||||
public $listitemclassname = '\qbank_managecategories\question_category_list_item';
|
||||
|
||||
/**
|
||||
* Reference to list displayed below this one.
|
||||
* @var $nextlist
|
||||
*/
|
||||
public $nextlist = null;
|
||||
|
||||
/**
|
||||
* Reference to list displayed above this one.
|
||||
* @var $lastlist
|
||||
*/
|
||||
public $lastlist = null;
|
||||
|
||||
/**
|
||||
* Context.
|
||||
* @var $context
|
||||
*/
|
||||
public $context = null;
|
||||
|
||||
/**
|
||||
* Sort by string.
|
||||
* @var $sortby
|
||||
*/
|
||||
public $sortby = 'parent, sortorder, name';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $attributes
|
||||
* @param boolean $editable
|
||||
* @param \moodle_url $pageurl url for this page
|
||||
* @param integer $page if 0 no pagination. (These three params only used in top level list.)
|
||||
* @param string $pageparamname name of url param that is used for passing page no
|
||||
* @param integer $itemsperpage no of top level items.
|
||||
* @param \context $context
|
||||
*/
|
||||
public function __construct($type='ul', $attributes='', $editable = false, $pageurl=null,
|
||||
$page = 0, $pageparamname = 'page', $itemsperpage = 20, $context = null) {
|
||||
parent::__construct('ul', '', $editable, $pageurl, $page, 'cpage', $itemsperpage);
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the array of records of list items.
|
||||
*/
|
||||
public function get_records() : void {
|
||||
$this->records = helper::get_categories_for_contexts($this->context->id, $this->sortby);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest category id that the $item can have as its parent.
|
||||
* Note: question categories cannot go higher than the TOP category.
|
||||
*
|
||||
* @param \list_item $item The item which its top level parent is going to be returned.
|
||||
* @return int
|
||||
*/
|
||||
public function get_top_level_parent_id($item) : int {
|
||||
// Put the item at the highest level it can go.
|
||||
$topcategory = question_get_top_category($item->item->contextid, true);
|
||||
return $topcategory->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process any actions.
|
||||
*
|
||||
* @param integer $left id of item to move left
|
||||
* @param integer $right id of item to move right
|
||||
* @param integer $moveup id of item to move up
|
||||
* @param integer $movedown id of item to move down
|
||||
* @return void
|
||||
*/
|
||||
public function process_actions($left, $right, $moveup, $movedown) : void {
|
||||
$category = new stdClass();
|
||||
if (!empty($left)) {
|
||||
// Moved Left (In to another category).
|
||||
$category->id = $left;
|
||||
$category->contextid = $this->context->id;
|
||||
$event = \core\event\question_category_moved::create_from_question_category_instance($category);
|
||||
$event->trigger();
|
||||
} else if (!empty($right)) {
|
||||
// Moved Right (Out of the current category).
|
||||
$category->id = $right;
|
||||
$category->contextid = $this->context->id;
|
||||
$event = \core\event\question_category_moved::create_from_question_category_instance($category);
|
||||
$event->trigger();
|
||||
}
|
||||
parent::process_actions($left, $right, $moveup, $movedown);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?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_managecategories;
|
||||
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* An item in a list of question categories.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_category_list_item extends \list_item {
|
||||
|
||||
/**
|
||||
* Override set_icon_html function.
|
||||
*
|
||||
* @param bool $first Is the first on the list.
|
||||
* @param bool $last Is the last on the list.
|
||||
* @param \list_item $lastitem Last item.
|
||||
*/
|
||||
public function set_icon_html($first, $last, $lastitem) : void {
|
||||
global $CFG;
|
||||
$category = $this->item;
|
||||
$url = new moodle_url('/question/bank/managecategories/category.php',
|
||||
($this->parentlist->pageurl->params() + ['edit' => $category->id]));
|
||||
$this->icons['edit'] = $this->image_icon(get_string('editthiscategory', 'question'), $url, 'edit');
|
||||
parent::set_icon_html($first, $last, $lastitem);
|
||||
$toplevel = ($this->parentlist->parentitem === null);// This is a top level item.
|
||||
if (($this->parentlist->nextlist !== null) && $last && $toplevel && (count($this->parentlist->items) > 1)) {
|
||||
$url = new moodle_url($this->parentlist->pageurl,
|
||||
[
|
||||
'movedowncontext' => $this->id,
|
||||
'tocontext' => $this->parentlist->nextlist->context->id,
|
||||
'sesskey' => sesskey()
|
||||
]
|
||||
);
|
||||
$this->icons['down'] = $this->image_icon(
|
||||
get_string('shareincontext', 'question',
|
||||
$this->parentlist->nextlist->context->get_context_name()), $url, 'down');
|
||||
}
|
||||
if (($this->parentlist->lastlist !== null) && $first && $toplevel && (count($this->parentlist->items) > 1)) {
|
||||
$url = new moodle_url($this->parentlist->pageurl,
|
||||
[
|
||||
'moveupcontext' => $this->id,
|
||||
'tocontext' => $this->parentlist->lastlist->context->id,
|
||||
'sesskey' => sesskey()
|
||||
]
|
||||
);
|
||||
$this->icons['up'] = $this->image_icon(
|
||||
get_string('shareincontext', 'question',
|
||||
$this->parentlist->lastlist->context->get_context_name()), $url, 'up');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override item_html function.
|
||||
*
|
||||
* @param array $extraargs
|
||||
* @return string Item html.
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function item_html($extraargs = []) : string {
|
||||
global $PAGE, $OUTPUT;
|
||||
$str = $extraargs['str'];
|
||||
$category = $this->item;
|
||||
|
||||
// Each section adds html to be displayed as part of this list item.
|
||||
$nodeparent = $PAGE->settingsnav->find('questionbank', \navigation_node::TYPE_CONTAINER);
|
||||
$questionbankurl = new moodle_url($nodeparent->action->get_path(), $this->parentlist->pageurl->params());
|
||||
$questionbankurl->param('cat', $category->id . ',' . $category->contextid);
|
||||
$categoryname = format_string($category->name, true, ['context' => $this->parentlist->context]);
|
||||
$idnumber = null;
|
||||
if ($category->idnumber !== null && $category->idnumber !== '') {
|
||||
$idnumber = $category->idnumber;
|
||||
}
|
||||
$questioncount = ' (' . $category->questioncount . ')';
|
||||
$categorydesc = format_text($category->info, $category->infoformat,
|
||||
['context' => $this->parentlist->context, 'noclean' => true]);
|
||||
|
||||
// Don't allow delete if this is the top category, or the last editable category in this context.
|
||||
$deleteurl = null;
|
||||
if ($category->parent && !helper::question_is_only_child_of_top_category_in_context($category->id)) {
|
||||
$deleteurl = new moodle_url($this->parentlist->pageurl, ['delete' => $this->id, 'sesskey' => sesskey()]);
|
||||
}
|
||||
|
||||
// Render each question category.
|
||||
$data =
|
||||
[
|
||||
'questionbankurl' => $questionbankurl,
|
||||
'categoryname' => $categoryname,
|
||||
'idnumber' => $idnumber,
|
||||
'questioncount' => $questioncount,
|
||||
'categorydesc' => $categorydesc,
|
||||
'deleteurl' => $deleteurl,
|
||||
'deletetitle' => $str->delete
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template(helper::PLUGINNAME . '/listitem', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
<?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_managecategories;
|
||||
|
||||
/**
|
||||
* QUESTION_PAGE_LENGTH - Number of categories to display on page.
|
||||
*/
|
||||
define('QUESTION_PAGE_LENGTH', 25);
|
||||
|
||||
use context;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use qbank_managecategories\form\question_category_edit_form;
|
||||
use question_bank;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class for performing operations on question categories.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_category_object {
|
||||
|
||||
/**
|
||||
* @var array common language strings.
|
||||
*/
|
||||
public $str;
|
||||
|
||||
/**
|
||||
* @var array nested lists to display categories.
|
||||
*/
|
||||
public $editlists = [];
|
||||
|
||||
/**
|
||||
* @var string tab.
|
||||
*/
|
||||
public $tab;
|
||||
|
||||
/**
|
||||
* @var int tab size.
|
||||
*/
|
||||
public $tabsize = 3;
|
||||
|
||||
/**
|
||||
* @var moodle_url Object representing url for this page
|
||||
*/
|
||||
public $pageurl;
|
||||
|
||||
/**
|
||||
* @var question_category_edit_form Object representing form for adding / editing categories.
|
||||
*/
|
||||
public $catform;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $page page number.
|
||||
* @param moodle_url $pageurl base URL of the display categories page. Used for redirects.
|
||||
* @param context[] $contexts contexts where the current user can edit categories.
|
||||
* @param int $currentcat id of the category to be edited. 0 if none.
|
||||
* @param int|null $defaultcategory id of the current category. null if none.
|
||||
* @param int $todelete id of the category to delete. 0 if none.
|
||||
* @param context[] $addcontexts contexts where the current user can add questions.
|
||||
*/
|
||||
public function __construct($page, $pageurl, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) {
|
||||
|
||||
$this->tab = str_repeat(' ', $this->tabsize);
|
||||
|
||||
$this->str = new stdClass();
|
||||
$this->str->course = get_string('course');
|
||||
$this->str->category = get_string('category', 'question');
|
||||
$this->str->categoryinfo = get_string('categoryinfo', 'question');
|
||||
$this->str->questions = get_string('questions', 'question');
|
||||
$this->str->add = get_string('add');
|
||||
$this->str->delete = get_string('delete');
|
||||
$this->str->moveup = get_string('moveup');
|
||||
$this->str->movedown = get_string('movedown');
|
||||
$this->str->edit = get_string('editthiscategory', 'question');
|
||||
$this->str->hide = get_string('hide');
|
||||
$this->str->order = get_string('order');
|
||||
$this->str->parent = get_string('parent', 'question');
|
||||
$this->str->add = get_string('add');
|
||||
$this->str->action = get_string('action');
|
||||
$this->str->top = get_string('top');
|
||||
$this->str->addcategory = get_string('addcategory', 'question');
|
||||
$this->str->editcategory = get_string('editcategory', 'question');
|
||||
$this->str->cancel = get_string('cancel');
|
||||
$this->str->editcategories = get_string('editcategories', 'question');
|
||||
$this->str->page = get_string('page');
|
||||
|
||||
$this->pageurl = $pageurl;
|
||||
|
||||
$this->initialize($page, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this classes general category-related variables
|
||||
*
|
||||
* @param int $page page number.
|
||||
* @param context[] $contexts contexts where the current user can edit categories.
|
||||
* @param int $currentcat id of the category to be edited. 0 if none.
|
||||
* @param int|null $defaultcategory id of the current category. null if none.
|
||||
* @param int $todelete id of the category to delete. 0 if none.
|
||||
* @param context[] $addcontexts contexts where the current user can add questions.
|
||||
*/
|
||||
public function initialize($page, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts): void {
|
||||
$lastlist = null;
|
||||
foreach ($contexts as $context) {
|
||||
$this->editlists[$context->id] =
|
||||
new question_category_list('ul', '', true, $this->pageurl, $page, 'cpage', QUESTION_PAGE_LENGTH, $context);
|
||||
$this->editlists[$context->id]->lastlist =& $lastlist;
|
||||
if ($lastlist !== null) {
|
||||
$lastlist->nextlist =& $this->editlists[$context->id];
|
||||
}
|
||||
$lastlist =& $this->editlists[$context->id];
|
||||
}
|
||||
|
||||
$count = 1;
|
||||
$paged = false;
|
||||
foreach ($this->editlists as $key => $list) {
|
||||
list($paged, $count) = $this->editlists[$key]->list_from_records($paged, $count);
|
||||
}
|
||||
$this->catform = new question_category_edit_form($this->pageurl, compact('contexts', 'currentcat'));
|
||||
if (!$currentcat) {
|
||||
$this->catform->set_data(['parent' => $defaultcategory]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the user interface.
|
||||
*
|
||||
*/
|
||||
public function display_user_interface(): void {
|
||||
|
||||
// Interface for editing existing categories.
|
||||
$this->output_edit_lists();
|
||||
|
||||
echo \html_writer::empty_tag('br');
|
||||
// Interface for adding a new category.
|
||||
$this->output_new_table();
|
||||
echo \html_writer::empty_tag('br');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a table to allow entry of a new category
|
||||
*/
|
||||
public function output_new_table(): void {
|
||||
$this->catform->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a list to allow editing/rearranging of existing categories.
|
||||
*
|
||||
* $this->initialize() must have already been called
|
||||
*
|
||||
*/
|
||||
public function output_edit_lists(): void {
|
||||
global $OUTPUT;
|
||||
|
||||
echo $OUTPUT->heading_with_help(get_string('editcategories', 'question'), 'editcategories', 'question');
|
||||
|
||||
foreach ($this->editlists as $context => $list) {
|
||||
$listhtml = $list->to_html(0, ['str' => $this->str]);
|
||||
if ($listhtml) {
|
||||
echo $OUTPUT->box_start('boxwidthwide boxaligncenter generalbox questioncategories contextlevel' .
|
||||
$list->context->contextlevel);
|
||||
$fullcontext = context::instance_by_id($context);
|
||||
echo $OUTPUT->heading(get_string('questioncatsfor', 'question', $fullcontext->get_context_name()), 3);
|
||||
echo $listhtml;
|
||||
echo $OUTPUT->box_end();
|
||||
}
|
||||
}
|
||||
echo $list->display_page_numbers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the courseids for the given categories.
|
||||
*
|
||||
* @param array $categories contains category objects in a tree representation
|
||||
* @return array courseids flat array in form categoryid=>courseid
|
||||
*/
|
||||
public function get_course_ids(array $categories): array {
|
||||
$courseids = [];
|
||||
foreach ($categories as $key => $cat) {
|
||||
$courseids[$key] = $cat->course;
|
||||
if (!empty($cat->children)) {
|
||||
$courseids = array_merge($courseids, $this->get_course_ids($cat->children));
|
||||
}
|
||||
}
|
||||
return $courseids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a category.
|
||||
*
|
||||
* @param int $categoryid Category id.
|
||||
*/
|
||||
public function edit_single_category(int $categoryid): void {
|
||||
// Interface for adding a new category.
|
||||
global $DB;
|
||||
// Interface for editing existing categories.
|
||||
$category = $DB->get_record("question_categories", ["id" => $categoryid]);
|
||||
if (empty($category)) {
|
||||
throw new moodle_exception('invalidcategory', '', '', $categoryid);
|
||||
} else if ($category->parent == 0) {
|
||||
throw new moodle_exception('cannotedittopcat', 'question', '', $categoryid);
|
||||
} else {
|
||||
$category->parent = "{$category->parent},{$category->contextid}";
|
||||
$category->submitbutton = get_string('savechanges');
|
||||
$category->categoryheader = $this->str->edit;
|
||||
$this->catform->set_data($category);
|
||||
$this->catform->display();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the viable parents.
|
||||
*
|
||||
* Viable parents are any except for the category itself, or any of it's descendants
|
||||
* The parentstrings parameter is passed by reference and changed by this function.
|
||||
*
|
||||
* @param array $parentstrings a list of parentstrings
|
||||
* @param object $category Category object
|
||||
*/
|
||||
public function set_viable_parents(array &$parentstrings, object $category): void {
|
||||
|
||||
unset($parentstrings[$category->id]);
|
||||
if (isset($category->children)) {
|
||||
foreach ($category->children as $child) {
|
||||
$this->set_viable_parents($parentstrings, $child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets question categories.
|
||||
*
|
||||
* @param int|null $parent - if given, restrict records to those with this parent id.
|
||||
* @param string $sort - [[sortfield [,sortfield]] {ASC|DESC}].
|
||||
* @return array categories.
|
||||
*/
|
||||
public function get_question_categories(int $parent = null, string $sort = "sortorder ASC"): array {
|
||||
global $COURSE, $DB;
|
||||
if (is_null($parent)) {
|
||||
$categories = $DB->get_records('question_categories', ['course' => $COURSE->id], $sort);
|
||||
} else {
|
||||
$select = "parent = ? AND course = ?";
|
||||
$categories = $DB->get_records_select('question_categories', $select, [$parent, $COURSE->id], $sort);
|
||||
}
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing question category.
|
||||
*
|
||||
* @param int $categoryid id of category to delete.
|
||||
*/
|
||||
public function delete_category(int $categoryid): void {
|
||||
global $CFG, $DB;
|
||||
helper::question_can_delete_cat($categoryid);
|
||||
if (!$category = $DB->get_record("question_categories", ["id" => $categoryid])) { // Security.
|
||||
throw new moodle_exception('unknowcategory');
|
||||
}
|
||||
// Send the children categories to live with their grandparent.
|
||||
$DB->set_field("question_categories", "parent", $category->parent, ["parent" => $category->id]);
|
||||
|
||||
// Finally delete the category itself.
|
||||
$DB->delete_records("question_categories", ["id" => $category->id]);
|
||||
|
||||
// Log the deletion of this category.
|
||||
$event = \core\event\question_category_deleted::create_from_question_category_instance($category);
|
||||
$event->add_record_snapshot('question_categories', $category);
|
||||
$event->trigger();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Move questions and then delete the category.
|
||||
*
|
||||
* @param int $oldcat id of the old category.
|
||||
* @param int $newcat id of the new category.
|
||||
*/
|
||||
public function move_questions_and_delete_category(int $oldcat, int $newcat): void {
|
||||
helper::question_can_delete_cat($oldcat);
|
||||
$this->move_questions($oldcat, $newcat);
|
||||
$this->delete_category($oldcat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form to move a category.
|
||||
*
|
||||
* @param int $questionsincategory
|
||||
* @param object $category
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function display_move_form($questionsincategory, $category): void {
|
||||
global $OUTPUT;
|
||||
$vars = new stdClass();
|
||||
$vars->name = $category->name;
|
||||
$vars->count = $questionsincategory;
|
||||
echo $OUTPUT->box(get_string('categorymove', 'question', $vars), 'generalbox boxaligncenter');
|
||||
$this->moveform->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move questions to another category.
|
||||
*
|
||||
* @param int $oldcat id of the old category.
|
||||
* @param int $newcat id of the new category.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function move_questions(int $oldcat, int $newcat): void {
|
||||
global $DB;
|
||||
$questionids = $DB->get_records_select_menu('question',
|
||||
'category = ? AND (parent = 0 OR parent = id)', [$oldcat], '', 'id,1');
|
||||
question_move_questions_to_category(array_keys($questionids), $newcat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new category.
|
||||
*
|
||||
* Data is expected to come from question_category_edit_form.
|
||||
*
|
||||
* By default redirects on success, unless $return is true.
|
||||
*
|
||||
* @param string $newparent 'categoryid,contextid' of the parent category.
|
||||
* @param string $newcategory the name.
|
||||
* @param string $newinfo the description.
|
||||
* @param bool $return if true, return rather than redirecting.
|
||||
* @param int|string $newinfoformat description format. One of the FORMAT_ constants.
|
||||
* @param null $idnumber the idnumber. '' is converted to null.
|
||||
* @return bool|int New category id if successful, else false.
|
||||
*/
|
||||
public function add_category($newparent, $newcategory, $newinfo, $return = false, $newinfoformat = FORMAT_HTML,
|
||||
$idnumber = null): int {
|
||||
global $DB;
|
||||
if (empty($newcategory)) {
|
||||
throw new moodle_exception('categorynamecantbeblank', 'question');
|
||||
}
|
||||
list($parentid, $contextid) = explode(',', $newparent);
|
||||
// ...moodle_form makes sure select element output is legal no need for further cleaning.
|
||||
require_capability('moodle/question:managecategory', context::instance_by_id($contextid));
|
||||
|
||||
if ($parentid) {
|
||||
if (!($DB->get_field('question_categories', 'contextid', ['id' => $parentid]) == $contextid)) {
|
||||
throw new moodle_exception('cannotinsertquestioncatecontext', 'question', '',
|
||||
['cat' => $newcategory, 'ctx' => $contextid]);
|
||||
}
|
||||
}
|
||||
|
||||
if ((string) $idnumber === '') {
|
||||
$idnumber = null;
|
||||
} else if (!empty($contextid)) {
|
||||
// While this check already exists in the form validation, this is a backstop preventing unnecessary errors.
|
||||
if ($DB->record_exists('question_categories',
|
||||
['idnumber' => $idnumber, 'contextid' => $contextid])) {
|
||||
$idnumber = null;
|
||||
}
|
||||
}
|
||||
|
||||
$cat = new stdClass();
|
||||
$cat->parent = $parentid;
|
||||
$cat->contextid = $contextid;
|
||||
$cat->name = $newcategory;
|
||||
$cat->info = $newinfo;
|
||||
$cat->infoformat = $newinfoformat;
|
||||
$cat->sortorder = 999;
|
||||
$cat->stamp = make_unique_id_code();
|
||||
$cat->idnumber = $idnumber;
|
||||
$categoryid = $DB->insert_record("question_categories", $cat);
|
||||
|
||||
// Log the creation of this category.
|
||||
$category = new stdClass();
|
||||
$category->id = $categoryid;
|
||||
$category->contextid = $contextid;
|
||||
$event = \core\event\question_category_created::create_from_question_category_instance($category);
|
||||
$event->trigger();
|
||||
|
||||
if ($return) {
|
||||
return $categoryid;
|
||||
} else {
|
||||
// Always redirect after successful action.
|
||||
redirect($this->pageurl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing category with given params.
|
||||
*
|
||||
* Warning! parameter order and meaning confusingly different from add_category in some ways!
|
||||
*
|
||||
* @param int $updateid id of the category to update.
|
||||
* @param int $newparent 'categoryid,contextid' of the parent category to set.
|
||||
* @param string $newname category name.
|
||||
* @param string $newinfo category description.
|
||||
* @param int|string $newinfoformat description format. One of the FORMAT_ constants.
|
||||
* @param int $idnumber the idnumber. '' is converted to null.
|
||||
* @param bool $redirect if true, will redirect once the DB is updated (default).
|
||||
*/
|
||||
public function update_category($updateid, $newparent, $newname, $newinfo, $newinfoformat = FORMAT_HTML,
|
||||
$idnumber = null, $redirect = true): void {
|
||||
global $CFG, $DB;
|
||||
if (empty($newname)) {
|
||||
throw new moodle_exception('categorynamecantbeblank', 'question');
|
||||
}
|
||||
|
||||
// Get the record we are updating.
|
||||
$oldcat = $DB->get_record('question_categories', ['id' => $updateid]);
|
||||
$lastcategoryinthiscontext = helper::question_is_only_child_of_top_category_in_context($updateid);
|
||||
|
||||
if (!empty($newparent) && !$lastcategoryinthiscontext) {
|
||||
list($parentid, $tocontextid) = explode(',', $newparent);
|
||||
} else {
|
||||
$parentid = $oldcat->parent;
|
||||
$tocontextid = $oldcat->contextid;
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
$fromcontext = context::instance_by_id($oldcat->contextid);
|
||||
require_capability('moodle/question:managecategory', $fromcontext);
|
||||
|
||||
// If moving to another context, check permissions some more, and confirm contextid,stamp uniqueness.
|
||||
$newstamprequired = false;
|
||||
if ($oldcat->contextid != $tocontextid) {
|
||||
$tocontext = context::instance_by_id($tocontextid);
|
||||
require_capability('moodle/question:managecategory', $tocontext);
|
||||
|
||||
// Confirm stamp uniqueness in the new context. If the stamp already exists, generate a new one.
|
||||
if ($DB->record_exists('question_categories', ['contextid' => $tocontextid, 'stamp' => $oldcat->stamp])) {
|
||||
$newstamprequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((string) $idnumber === '') {
|
||||
$idnumber = null;
|
||||
} else if (!empty($tocontextid)) {
|
||||
// While this check already exists in the form validation, this is a backstop preventing unnecessary errors.
|
||||
if ($DB->record_exists_select('question_categories',
|
||||
'idnumber = ? AND contextid = ? AND id <> ?',
|
||||
[$idnumber, $tocontextid, $updateid])) {
|
||||
$idnumber = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the category record.
|
||||
$cat = new stdClass();
|
||||
$cat->id = $updateid;
|
||||
$cat->name = $newname;
|
||||
$cat->info = $newinfo;
|
||||
$cat->infoformat = $newinfoformat;
|
||||
$cat->parent = $parentid;
|
||||
$cat->contextid = $tocontextid;
|
||||
$cat->idnumber = $idnumber;
|
||||
if ($newstamprequired) {
|
||||
$cat->stamp = make_unique_id_code();
|
||||
}
|
||||
$DB->update_record('question_categories', $cat);
|
||||
|
||||
// Log the update of this category.
|
||||
$event = \core\event\question_category_updated::create_from_question_category_instance($cat);
|
||||
$event->trigger();
|
||||
|
||||
// If the category name has changed, rename any random questions in that category.
|
||||
if ($oldcat->name != $cat->name) {
|
||||
$where = "qtype = 'random' AND category = ? AND " . $DB->sql_compare_text('questiontext') . " = ?";
|
||||
|
||||
$randomqtype = question_bank::get_qtype('random');
|
||||
$randomqname = $randomqtype->question_name($cat, false);
|
||||
$DB->set_field_select('question', 'name', $randomqname, $where, [$cat->id, '0']);
|
||||
|
||||
$randomqname = $randomqtype->question_name($cat, true);
|
||||
$DB->set_field_select('question', 'name', $randomqname, $where, [$cat->id, '1']);
|
||||
}
|
||||
|
||||
if ($oldcat->contextid != $tocontextid) {
|
||||
// Moving to a new context. Must move files belonging to questions.
|
||||
question_move_category_to_context($cat->id, $oldcat->contextid, $tocontextid);
|
||||
}
|
||||
|
||||
// Cat param depends on the context id, so update it.
|
||||
$this->pageurl->param('cat', $updateid . ',' . $tocontextid);
|
||||
if ($redirect) {
|
||||
// Always redirect after successful action.
|
||||
redirect($this->pageurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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_managecategories', language 'en'
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Guillermo Gomez Arias <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Manage categories';
|
||||
$string['privacy:metadata'] = 'The Manage Categories plugin does not store any user data.';
|
||||
@@ -0,0 +1,57 @@
|
||||
{{!
|
||||
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_managecategories/listitem
|
||||
|
||||
This template renders the list item for each category.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"questionbankurl": "question/edit.php?cmid=",
|
||||
"categoryname": "Default for Miscellaneous",
|
||||
"idnumber": "1",
|
||||
"questioncount": "1",
|
||||
"categorydesc": "The default category for questions shared in context Miscellaneous",
|
||||
"deleteurl": "url",
|
||||
"deletetitle": "Advanced"
|
||||
}
|
||||
}}
|
||||
|
||||
<b>
|
||||
<a title="{{#str}}editquestions, question{{/str}}" href="{{{questionbankurl}}}">
|
||||
{{categoryname}}
|
||||
{{#idnumber}}
|
||||
<span class="badge badge-primary">
|
||||
<span class="accesshide">
|
||||
{{#str}}idnumber, question{{/str}}
|
||||
</span>
|
||||
{{idnumber}}
|
||||
</span>
|
||||
{{/idnumber}}
|
||||
{{questioncount}}
|
||||
</a>
|
||||
</b>
|
||||
{{#categorydesc}}
|
||||
<div class="text_to_html">
|
||||
{{{categorydesc}}}
|
||||
</div>
|
||||
{{/categorydesc}}
|
||||
{{#deleteurl}}
|
||||
<a title="{{deletetitle}}" href="{{{deleteurl}}}">
|
||||
{{# pix }} t/delete, core, {{deletetitle}} {{/ pix }}
|
||||
</a>
|
||||
{{/deleteurl}}
|
||||
@@ -0,0 +1,66 @@
|
||||
@qbank @qbank_managecategories @javascript
|
||||
Feature: A teacher can put questions in categories in the question bank
|
||||
In order to organize my questions
|
||||
As a teacher
|
||||
I create and edit categories and move questions between them
|
||||
|
||||
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 "question categories" exist:
|
||||
| contextlevel | reference | questioncategory | name |
|
||||
| Course | C1 | Top | top |
|
||||
| Course | C1 | top | Default for C1 |
|
||||
| Course | C1 | Default for C1 | Subcategory |
|
||||
| Course | C1 | top | Used category |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Used category | essay | Test question to be moved | Write about whatever you want |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
Scenario: A new question category can be created
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Name | New Category 1 |
|
||||
| Parent category | Top |
|
||||
| Category info | Created as a test |
|
||||
| ID number | newcatidnumber |
|
||||
And I press "submitbutton"
|
||||
Then I should see "New Category 1"
|
||||
And I should see "ID number"
|
||||
And I should see "newcatidnumber"
|
||||
And I should see "(0)"
|
||||
And I should see "Created as a test" in the "New Category 1" "list_item"
|
||||
And "New Category 1 [newcatidnumber]" "option" should exist in the "Parent category" "select"
|
||||
|
||||
Scenario: A question category can be edited
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Edit this category" "link" in the "Subcategory" "list_item"
|
||||
And the field "parent" matches value " Default for C1"
|
||||
And I set the following fields to these values:
|
||||
| Name | New name |
|
||||
| Category info | I was edited |
|
||||
And I press "Save changes"
|
||||
Then I should see "New name"
|
||||
And I should see "I was edited" in the "New name" "list_item"
|
||||
|
||||
Scenario: An empty question category can be deleted
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Delete" "link" in the "Subcategory" "list_item"
|
||||
Then I should not see "Subcategory"
|
||||
|
||||
Scenario: An non-empty question category can be deleted if you move the contents elsewhere
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Delete" "link" in the "Used category" "list_item"
|
||||
And I should see "The category 'Used category' contains 1 questions"
|
||||
And I press "Save in category"
|
||||
Then I should not see "Used category"
|
||||
And I should see "Default for C1 (1)"
|
||||
@@ -0,0 +1,52 @@
|
||||
@qbank @qbank_managecategories
|
||||
Feature: A teacher can put questions with idnumbers in categories with idnumbers in the question bank
|
||||
In order to organize my questions
|
||||
As a teacher
|
||||
I create and edit categories (now with idnumbers)
|
||||
|
||||
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 I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
Scenario: A new question category can only be created with a unique idnumber for a context
|
||||
# Note need to create the top category each time.
|
||||
When the following "question categories" exist:
|
||||
| contextlevel | reference | questioncategory | name | idnumber |
|
||||
| Course | C1 | Top | top | |
|
||||
| Course | C1 | top | Used category | c1used |
|
||||
And I navigate to "Question bank > Categories" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Name | Sub used category |
|
||||
| Parent category | Used category |
|
||||
| Category info | Created as a test |
|
||||
| ID number | c1used |
|
||||
And I press "Add category"
|
||||
# Standard warning.
|
||||
Then I should see "This ID number is already in use"
|
||||
# Correction to a unique idnumber for the context.
|
||||
And I set the field "ID number" to "c1unused"
|
||||
And I press "Add category"
|
||||
Then I should see "Sub used category"
|
||||
And I should see "ID number"
|
||||
And I should see "c1unused"
|
||||
And I should see "(0)"
|
||||
And I should see "Created as a test" in the "Sub used category" "list_item"
|
||||
|
||||
Scenario: A question category can be edited and saved without changing the idnumber
|
||||
When the following "question categories" exist:
|
||||
| contextlevel | reference | questioncategory | name | idnumber |
|
||||
| Course | C1 | Top | top | |
|
||||
| Course | C1 | top | Used category | c1used |
|
||||
And I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Edit this category" "link" in the "Used category" "list_item"
|
||||
And I press "Save changes"
|
||||
Then I should not see "This ID number is already in use"
|
||||
@@ -0,0 +1,50 @@
|
||||
@qbank @qbank_managecategories @javascript
|
||||
Feature: Use the qbank plugin manager page for managecategories
|
||||
In order to check the plugin behaviour with enable and disable
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber |
|
||||
| quiz | Test quiz | C1 | quiz1 |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
|
||||
Scenario: Enable/disable managecategories plugin from the base view
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I should see "Manage categories"
|
||||
And I click on "Disable" "link" in the "Manage categories" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank" in current page administration
|
||||
Then I should not see "Categories"
|
||||
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I click on "Enable" "link" in the "Manage categories" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank" in current page administration
|
||||
And I should see "Categories"
|
||||
|
||||
Scenario: Enable/disable the tab New category when tyring to add a random question to a quiz
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I should see "Manage categories"
|
||||
And I click on "Disable" "link" in the "Manage categories" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Edit quiz" in current page administration
|
||||
And I open the "last" add to quiz menu
|
||||
And I follow "a random question"
|
||||
Then I should not see "New category"
|
||||
And I press "id_cancel"
|
||||
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I click on "Enable" "link" in the "Manage categories" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Edit quiz" in current page administration
|
||||
And I open the "last" add to quiz menu
|
||||
And I follow "a random question"
|
||||
And I should see "New category"
|
||||
@@ -0,0 +1,228 @@
|
||||
<?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_managecategories;
|
||||
|
||||
/**
|
||||
* Unit tests for helper class.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2006 The Open University
|
||||
* @author 2021, Guillermo Gomez Arias <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \qbank_managecategories\helper
|
||||
*/
|
||||
class helper_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* @var \context_module module context.
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var \stdClass course object.
|
||||
*/
|
||||
protected $course;
|
||||
|
||||
/**
|
||||
* @var \component_generator_base question generator.
|
||||
*/
|
||||
protected $qgenerator;
|
||||
|
||||
/**
|
||||
* @var \stdClass quiz object.
|
||||
*/
|
||||
protected $quiz;
|
||||
|
||||
/**
|
||||
* Tests initial setup.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
self::setAdminUser();
|
||||
$this->resetAfterTest();
|
||||
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$this->course = $datagenerator->create_course();
|
||||
$this->quiz = $datagenerator->create_module('quiz', ['course' => $this->course->id]);
|
||||
$this->qgenerator = $datagenerator->get_plugin_generator('core_question');
|
||||
$this->context = \context_module::instance($this->quiz->cmid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test question_remove_stale_questions_from_category function.
|
||||
*
|
||||
* @covers ::question_remove_stale_questions_from_category
|
||||
*/
|
||||
public function test_question_remove_stale_questions_from_category() {
|
||||
global $DB;
|
||||
|
||||
$qcat1 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$q1a = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]);
|
||||
|
||||
$qcat2 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$q2a = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden.
|
||||
$q2b = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]);
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]);
|
||||
quiz_add_quiz_question($q2b->id, $this->quiz);
|
||||
quiz_add_random_questions($this->quiz, 0, $qcat2->id, 1, false);
|
||||
|
||||
// We added one random question to the quiz and we expect the quiz to have only one random question.
|
||||
$q2d = $DB->get_record_sql("SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {quiz_slots} s ON s.questionid = q.id
|
||||
WHERE q.qtype = :qtype
|
||||
AND s.quizid = :quizid",
|
||||
['qtype' => 'random', 'quizid' => $this->quiz->id], MUST_EXIST);
|
||||
|
||||
// The following 2 lines have to be after the quiz_add_random_questions() call above.
|
||||
// Otherwise, quiz_add_random_questions() will to be "smart" and use them instead of creating a new "random" question.
|
||||
$q1b = $this->qgenerator->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
|
||||
$q2c = $this->qgenerator->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
|
||||
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
|
||||
// Non-existing category, nothing will happen.
|
||||
helper::question_remove_stale_questions_from_category(0);
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
|
||||
// First category, should be empty afterwards.
|
||||
helper::question_remove_stale_questions_from_category($qcat1->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q1a->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q1b->id]));
|
||||
|
||||
// Second category, used questions should be left untouched.
|
||||
helper::question_remove_stale_questions_from_category($qcat2->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q2a->id]));
|
||||
$this->assertTrue($DB->record_exists('question', ['id' => $q2b->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q2c->id]));
|
||||
$this->assertTrue($DB->record_exists('question', ['id' => $q2d->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete top category in function question_can_delete_cat.
|
||||
*
|
||||
* @covers ::question_can_delete_cat
|
||||
* @covers ::question_is_top_category
|
||||
*/
|
||||
public function test_question_can_delete_cat_top_category() {
|
||||
|
||||
$qcategory1 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
|
||||
// Try to delete a top category.
|
||||
$categorytop = question_get_top_category($qcategory1->id, true)->id;
|
||||
$this->expectException('moodle_exception');
|
||||
$this->expectExceptionMessage(get_string('cannotdeletetopcat', 'question'));
|
||||
helper::question_can_delete_cat($categorytop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete only child category in function question_can_delete_cat.
|
||||
*
|
||||
* @covers ::question_can_delete_cat
|
||||
* @covers ::question_is_only_child_of_top_category_in_context
|
||||
*/
|
||||
public function test_question_can_delete_cat_child_category() {
|
||||
|
||||
$qcategory1 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
|
||||
// Try to delete an only child of top category having also at least one child.
|
||||
$this->expectException('moodle_exception');
|
||||
$this->expectExceptionMessage(get_string('cannotdeletecate', 'question'));
|
||||
helper::question_can_delete_cat($qcategory1->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete category in function question_can_delete_cat without capabilities.
|
||||
*
|
||||
* @covers ::question_can_delete_cat
|
||||
*/
|
||||
public function test_question_can_delete_cat_capability() {
|
||||
|
||||
$qcategory1 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$qcategory2 = $this->qgenerator->create_question_category(['contextid' => $this->context->id, 'parent' => $qcategory1->id]);
|
||||
|
||||
// This call should not throw an exception as admin user has the capabilities moodle/question:managecategory.
|
||||
helper::question_can_delete_cat($qcategory2->id);
|
||||
|
||||
// Try to delete a category with and user without the capability.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
$this->expectExceptionMessage(get_string('nopermissions', 'error', get_string('question:managecategory', 'role')));
|
||||
helper::question_can_delete_cat($qcategory2->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test question_category_select_menu function.
|
||||
*
|
||||
* @covers ::question_category_select_menu
|
||||
* @covers ::question_category_options
|
||||
*/
|
||||
public function test_question_category_select_menu() {
|
||||
|
||||
$this->qgenerator->create_question_category(['contextid' => $this->context->id, 'name' => 'Test this question category']);
|
||||
$contexts = new \question_edit_contexts($this->context);
|
||||
|
||||
ob_start();
|
||||
helper::question_category_select_menu($contexts->having_cap('moodle/question:add'));
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Test the select menu of question categories output.
|
||||
$this->assertStringContainsString('Question category', $output);
|
||||
$this->assertStringContainsString('<option selected="selected" value="">choosedots</option>', $output);
|
||||
$this->assertStringContainsString('Test this question category', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that question_category_options function returns the correct category tree.
|
||||
*
|
||||
* @covers ::question_category_options
|
||||
* @covers ::get_categories_for_contexts
|
||||
* @covers ::question_fix_top_names
|
||||
* @covers ::question_add_context_in_key
|
||||
* @covers ::add_indented_names
|
||||
*/
|
||||
public function test_question_category_options() {
|
||||
|
||||
$qcategory1 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$qcategory2 = $this->qgenerator->create_question_category(['contextid' => $this->context->id, 'parent' => $qcategory1->id]);
|
||||
$qcategory3 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
|
||||
$contexts = new \question_edit_contexts($this->context);
|
||||
|
||||
// Validate that we have the array with the categories tree.
|
||||
$categorycontexts = helper::question_category_options($contexts->having_cap('moodle/question:add'));
|
||||
foreach ($categorycontexts as $categorycontext) {
|
||||
$this->assertCount(3, $categorycontext);
|
||||
}
|
||||
|
||||
// Validate that we have the array with the categories tree and that top category is there.
|
||||
$categorycontexts = helper::question_category_options($contexts->having_cap('moodle/question:add'), true);
|
||||
foreach ($categorycontexts as $categorycontext) {
|
||||
$this->assertCount(4, $categorycontext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?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_managecategories;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/question/editlib.php');
|
||||
|
||||
use context;
|
||||
use context_course;
|
||||
use context_module;
|
||||
use moodle_url;
|
||||
use question_edit_contexts;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Unit tests for qbank_managecategories\question_category_object.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2019 the Open University
|
||||
* @author 2021, Guillermo Gomez Arias <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \qbank_managecategories\question_category_object
|
||||
*/
|
||||
class question_category_object_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* @var question_category_object used in the tests.
|
||||
*/
|
||||
protected $qcobject;
|
||||
|
||||
/**
|
||||
* @var context a context to use.
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var stdClass top category in context.
|
||||
*/
|
||||
protected $topcat;
|
||||
|
||||
/**
|
||||
* @var stdClass course object.
|
||||
*/
|
||||
protected $course;
|
||||
|
||||
/**
|
||||
* @var stdClass quiz object.
|
||||
*/
|
||||
protected $quiz;
|
||||
|
||||
/**
|
||||
* @var question_edit_contexts
|
||||
*/
|
||||
private $qcontexts;
|
||||
|
||||
/**
|
||||
* @var false|object|stdClass|null
|
||||
*/
|
||||
private $defaultcategoryobj;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $defaultcategory;
|
||||
|
||||
/**
|
||||
* @var question_category_object
|
||||
*/
|
||||
private $qcobjectquiz;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
self::setAdminUser();
|
||||
$this->resetAfterTest();
|
||||
$this->context = context_course::instance(SITEID);
|
||||
$contexts = new question_edit_contexts($this->context);
|
||||
$this->topcat = question_get_top_category($this->context->id, true);
|
||||
$this->qcobject = new question_category_object(null,
|
||||
new moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID]),
|
||||
$contexts->having_one_edit_tab_cap('categories'), 0, null, 0,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
|
||||
// Set up tests in a quiz context.
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
$this->quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $this->course->id]);
|
||||
$this->qcontexts = new question_edit_contexts(context_module::instance($this->quiz->cmid));
|
||||
|
||||
$this->defaultcategoryobj = question_make_default_categories([$this->qcontexts->lowest()]);
|
||||
$this->defaultcategory = $this->defaultcategoryobj->id . ',' . $this->defaultcategoryobj->contextid;
|
||||
|
||||
$this->qcobjectquiz = new question_category_object(
|
||||
1,
|
||||
new moodle_url('/mod/quiz/edit.php', ['cmid' => $this->quiz->cmid]),
|
||||
$this->qcontexts->having_one_edit_tab_cap('categories'),
|
||||
$this->defaultcategoryobj->id,
|
||||
$this->defaultcategory,
|
||||
null,
|
||||
$this->qcontexts->having_cap('moodle/question:add'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a category.
|
||||
*
|
||||
* @covers ::add_category
|
||||
*/
|
||||
public function test_add_category_no_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New category', '', true, FORMAT_HTML, ''); // No idnumber passed as '' to match form data.
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New category', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a category with a tricky idnumber.
|
||||
*
|
||||
* @covers ::add_category
|
||||
*/
|
||||
public function test_add_category_set_idnumber_0() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New category', '', true, FORMAT_HTML, '0');
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New category', $newcat->name);
|
||||
$this->assertSame('0', $newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to add a category with duplicate idnumber blanks it.
|
||||
* (In reality, this would probably get caught by form validation.)
|
||||
*
|
||||
* @covers ::add_category
|
||||
*/
|
||||
public function test_add_category_try_to_set_duplicate_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Existing category', '', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New category', '', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New category', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a category.
|
||||
*
|
||||
* @covers ::update_category
|
||||
*/
|
||||
public function test_update_category() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Old name', 'Description', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', 'New description', FORMAT_HTML, '0', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertSame('0', $newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a category to remove the idnumber.
|
||||
*
|
||||
* @covers ::update_category
|
||||
*/
|
||||
public function test_update_category_removing_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Old name', 'Description', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', 'New description', FORMAT_HTML, '', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a category without changing the idnumber.
|
||||
*
|
||||
* @covers ::update_category
|
||||
*/
|
||||
public function test_update_category_dont_change_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Old name', 'Description', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', 'New description', FORMAT_HTML, 'frog', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertSame('frog', $newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to update a category so its idnumber duplicates idnumber blanks it.
|
||||
* (In reality, this would probably get caught by form validation.)
|
||||
*
|
||||
* @covers ::update_category
|
||||
*/
|
||||
public function test_update_category_try_to_set_duplicate_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Existing category', '', true, FORMAT_HTML, 'toad');
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'old name', '', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', '', FORMAT_HTML, 'toad', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category created event.
|
||||
*
|
||||
* @covers ::add_category
|
||||
*/
|
||||
public function test_question_category_created() {
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_created', $event);
|
||||
$this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context());
|
||||
$expected = [$this->course->id, 'quiz', 'addcategory', 'view.php?id=' . $this->quiz->cmid , $categoryid, $this->quiz->cmid];
|
||||
$this->assertEventLegacyLogData($expected, $event);
|
||||
$this->assertEventContextNotUsed($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category deleted event.
|
||||
*
|
||||
* @covers ::delete_category
|
||||
*/
|
||||
public function test_question_category_deleted() {
|
||||
// Create the category.
|
||||
$categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$this->qcobjectquiz->delete_category($categoryid);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_deleted', $event);
|
||||
$this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context());
|
||||
$this->assertEquals($categoryid, $event->objectid);
|
||||
$this->assertDebuggingNotCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category updated event.
|
||||
*
|
||||
* @covers ::update_category
|
||||
*/
|
||||
public function test_question_category_updated() {
|
||||
// Create the category.
|
||||
$categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$this->qcobjectquiz->update_category($categoryid, $this->defaultcategory, 'updatedcategory', '', FORMAT_HTML, '', false);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_updated', $event);
|
||||
$this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context());
|
||||
$this->assertEquals($categoryid, $event->objectid);
|
||||
$this->assertDebuggingNotCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category viewed event.
|
||||
* There is no external API for viewing the category, so the unit test will simply
|
||||
* create and trigger the event and ensure data is returned as expected.
|
||||
*
|
||||
* @covers ::add_category
|
||||
*/
|
||||
public function test_question_category_viewed() {
|
||||
// Create the category.
|
||||
$categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true);
|
||||
|
||||
// Log the view of this category.
|
||||
$category = new stdClass();
|
||||
$category->id = $categoryid;
|
||||
$context = context_module::instance($this->quiz->cmid);
|
||||
$event = \core\event\question_category_viewed::create_from_question_category_instance($category, $context);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$event->trigger();
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_viewed', $event);
|
||||
$this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context());
|
||||
$this->assertEquals($categoryid, $event->objectid);
|
||||
$this->assertDebuggingNotCalled();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Plugin version and other meta-data are defined here.
|
||||
*
|
||||
* @package qbank_managecategories
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Guillermo Gomez Arias <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'qbank_managecategories';
|
||||
$plugin->version = 2021070700;
|
||||
$plugin->requires = 2021052500;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
@@ -39,6 +39,9 @@ require_once($CFG->dirroot . '/question/move_form.php');
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see \qbank_managecategories\question_category_list
|
||||
* @todo deprecation on MDL-71679
|
||||
*/
|
||||
class question_category_list extends moodle_list {
|
||||
public $table = "question_categories";
|
||||
@@ -56,6 +59,9 @@ class question_category_list extends moodle_list {
|
||||
public $sortby = 'parent, sortorder, name';
|
||||
|
||||
public function __construct($type='ul', $attributes='', $editable = false, $pageurl=null, $page = 0, $pageparamname = 'page', $itemsperpage = 20, $context = null){
|
||||
debugging('Class question_category_list in \core_question\category_class is deprecated,
|
||||
please use qbank_managecategories\question_category_list instead.', DEBUG_DEVELOPER);
|
||||
|
||||
parent::__construct('ul', '', $editable, $pageurl, $page, 'cpage', $itemsperpage);
|
||||
$this->context = $context;
|
||||
}
|
||||
@@ -111,9 +117,14 @@ class question_category_list extends moodle_list {
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see \qbank_managecategories\question_category_list_item
|
||||
* @todo deprecation on MDL-71679
|
||||
*/
|
||||
class question_category_list_item extends list_item {
|
||||
public function set_icon_html($first, $last, $lastitem){
|
||||
debugging('Function set_icon_html() in \core_question\category_class is deprecated,
|
||||
please use qbank_managecategories\question_category_list_item::set_icon_html() instead.', DEBUG_DEVELOPER);
|
||||
global $CFG;
|
||||
$category = $this->item;
|
||||
$url = new moodle_url('/question/category.php', ($this->parentlist->pageurl->params() + array('edit'=>$category->id)));
|
||||
@@ -134,6 +145,8 @@ class question_category_list_item extends list_item {
|
||||
|
||||
public function item_html($extraargs = array()){
|
||||
global $CFG, $PAGE, $OUTPUT;
|
||||
debugging('Function item_html() in \core_question\category_class is deprecated,
|
||||
please use qbank_managecategories\question_category_list_item::item_html() instead.', DEBUG_DEVELOPER);
|
||||
$str = $extraargs['str'];
|
||||
$category = $this->item;
|
||||
|
||||
@@ -174,6 +187,9 @@ class question_category_list_item extends list_item {
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see \qbank_managecategories\question_category_object
|
||||
* @todo deprecation on MDL-71679
|
||||
*/
|
||||
class question_category_object {
|
||||
|
||||
@@ -211,6 +227,8 @@ class question_category_object {
|
||||
* @param context[] $addcontexts contexts where the current user can add questions.
|
||||
*/
|
||||
public function __construct($page, $pageurl, $contexts, $currentcat, $defaultcategory, $todelete, $addcontexts) {
|
||||
debugging('Class question_category_list in \core_question\category_class is deprecated,
|
||||
please use qbank_managecategories\question_category_object instead.', DEBUG_DEVELOPER);
|
||||
|
||||
$this->tab = str_repeat(' ', $this->tabsize);
|
||||
|
||||
|
||||
@@ -34,10 +34,15 @@ require_once($CFG->libdir.'/formslib.php');
|
||||
*
|
||||
* @copyright 2007 Jamie Pratt [email protected]
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\form\category_form
|
||||
*/
|
||||
class question_category_edit_form extends moodleform {
|
||||
|
||||
protected function definition() {
|
||||
debugging('Class question_export_form in \core_question\category_form is deprecated,
|
||||
please use core_question\bank\managecategories\form\category_form instead.', DEBUG_DEVELOPER);
|
||||
|
||||
$mform = $this->_form;
|
||||
|
||||
$contexts = $this->_customdata['contexts'];
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace core_question\bank\search;
|
||||
|
||||
use qbank_managecategories\helper;
|
||||
|
||||
/**
|
||||
* This class controls from which category questions are listed.
|
||||
*
|
||||
@@ -116,7 +118,7 @@ class category_condition extends condition {
|
||||
public function display_options() {
|
||||
global $PAGE;
|
||||
$displaydata = [];
|
||||
$catmenu = question_category_options($this->contexts, true, 0,
|
||||
$catmenu = helper::question_category_options($this->contexts, true, 0,
|
||||
true, -1, false);
|
||||
$displaydata['categoryselect'] = \html_writer::select($catmenu, 'category', $this->cat, [],
|
||||
array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace core_question\local\bank;
|
||||
|
||||
use core_question\bank\search\condition;
|
||||
use qbank_editquestion\editquestion_helper;
|
||||
use qbank_managecategories\helper;
|
||||
|
||||
/**
|
||||
* This class prints a view of the question bank.
|
||||
@@ -1057,7 +1058,7 @@ class view {
|
||||
'data-toggle' => 'action',
|
||||
'disabled' => true,
|
||||
]);
|
||||
question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}");
|
||||
helper::question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-18
@@ -99,15 +99,16 @@ function get_questions_category( $category, $noparent=false, $recurse=true, $exp
|
||||
*
|
||||
* @param int $categoryid a category id.
|
||||
* @return bool
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_is_only_child_of_top_category_in_context($categoryid) {
|
||||
global $DB;
|
||||
return 1 == $DB->count_records_sql("
|
||||
SELECT count(*)
|
||||
FROM {question_categories} c
|
||||
JOIN {question_categories} p ON c.parent = p.id
|
||||
JOIN {question_categories} s ON s.parent = c.parent
|
||||
WHERE c.id = ? AND p.parent = 0", array($categoryid));
|
||||
debugging('Function question_is_only_child_of_top_category_in_context()
|
||||
has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_is_only_child_of_top_category_in_context() instead.',
|
||||
DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::question_is_only_child_of_top_category_in_contextt($categoryid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,27 +116,28 @@ function question_is_only_child_of_top_category_in_context($categoryid) {
|
||||
*
|
||||
* @param int $categoryid a category id.
|
||||
* @return bool
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_is_top_category($categoryid) {
|
||||
global $DB;
|
||||
return 0 == $DB->get_field('question_categories', 'parent', array('id' => $categoryid));
|
||||
debugging('Function question_is_top_category() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_is_top_category() instead.', DEBUG_DEVELOPER);
|
||||
return \qbank_managecategories\helper::question_is_top_category($categoryid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that this user is allowed to delete this category.
|
||||
*
|
||||
* @param int $todelete a category id.
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\helper
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_can_delete_cat($todelete) {
|
||||
global $DB;
|
||||
if (question_is_top_category($todelete)) {
|
||||
print_error('cannotdeletetopcat', 'question');
|
||||
} else if (question_is_only_child_of_top_category_in_context($todelete)) {
|
||||
print_error('cannotdeletecate', 'question');
|
||||
} else {
|
||||
$contextid = $DB->get_field('question_categories', 'contextid', array('id' => $todelete));
|
||||
require_capability('moodle/question:managecategory', context::instance_by_id($contextid));
|
||||
}
|
||||
debugging('Function question_can_delete_cat() has been deprecated and moved to qbank_managecategories plugin,
|
||||
Please use qbank_managecategories\helper::question_can_delete_cat() instead.', DEBUG_DEVELOPER);
|
||||
\qbank_managecategories\helper::question_can_delete_cat($todelete);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1065,7 +1065,7 @@ class qformat_default {
|
||||
* back into an array of category names.
|
||||
*
|
||||
* Each category name is cleaned by a call to clean_param(, PARAM_TEXT),
|
||||
* which matches the cleaning in question/category_form.php.
|
||||
* which matches the cleaning in question/bank/managecategories/category_form.php.
|
||||
*
|
||||
* @param string $path
|
||||
* @return array of category names.
|
||||
|
||||
@@ -34,9 +34,14 @@ require_once($CFG->libdir . '/formslib.php');
|
||||
*
|
||||
* @copyright 2008 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 4.0 MDL-71585
|
||||
* @see qbank_managecategories\form\question_move_form
|
||||
*/
|
||||
class question_move_form extends moodleform {
|
||||
protected function definition() {
|
||||
debugging('Class question_move_form in \core_question\move_form is deprecated,
|
||||
please use qbank_managecategories\form\question_move_form instead.', DEBUG_DEVELOPER);
|
||||
|
||||
$mform = $this->_form;
|
||||
|
||||
$currentcat = $this->_customdata['currentcat'];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@core @core_question @javascript
|
||||
Feature: A teacher can put questions in categories in the question bank
|
||||
@core @core_question
|
||||
Feature: A teacher can move questions between categories in the question bank
|
||||
In order to organize my questions
|
||||
As a teacher
|
||||
I create and edit categories and move questions between them
|
||||
I move questions between categories
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
@@ -26,42 +26,6 @@ Feature: A teacher can put questions in categories in the question bank
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
Scenario: A new question category can be created
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Name | New Category 1 |
|
||||
| Parent category | Top |
|
||||
| Category info | Created as a test |
|
||||
| ID number | newcatidnumber |
|
||||
And I press "submitbutton"
|
||||
Then I should see "New Category 1 ID number newcatidnumber (0)"
|
||||
And I should see "Created as a test" in the "New Category 1" "list_item"
|
||||
And "New Category 1 [newcatidnumber]" "option" should exist in the "Parent category" "select"
|
||||
|
||||
Scenario: A question category can be edited
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Edit this category" "link" in the "Subcategory" "list_item"
|
||||
And the field "parent" matches value " Default for C1"
|
||||
And I set the following fields to these values:
|
||||
| Name | New name |
|
||||
| Category info | I was edited |
|
||||
And I press "Save changes"
|
||||
Then I should see "New name"
|
||||
And I should see "I was edited" in the "New name" "list_item"
|
||||
|
||||
Scenario: An empty question category can be deleted
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Delete" "link" in the "Subcategory" "list_item"
|
||||
Then I should not see "Subcategory"
|
||||
|
||||
Scenario: An non-empty question category can be deleted if you move the contents elsewhere
|
||||
When I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Delete" "link" in the "Used category" "list_item"
|
||||
And I should see "The category 'Used category' contains 1 questions"
|
||||
And I press "Save in category"
|
||||
Then I should not see "Used category"
|
||||
And I should see "Default for C1 (1)"
|
||||
|
||||
@javascript
|
||||
Scenario: Move a question between categories via the question page
|
||||
When I navigate to "Question bank > Questions" in current page administration
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@core @core_question
|
||||
Feature: A teacher can put questions with idnumbers in categories with idnumbers in the question bank
|
||||
Feature: A teacher can put questions with idnumbers in categories in the question bank
|
||||
In order to organize my questions
|
||||
As a teacher
|
||||
I create and edit categories and move questions between them (now with idnumbers)
|
||||
I move questions between categories (now with idnumbers)
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
@@ -17,37 +17,6 @@ Feature: A teacher can put questions with idnumbers in categories with idnumbers
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
Scenario: A new question category can only be created with a unique idnumber for a context
|
||||
# Note need to create the top category each time.
|
||||
When the following "question categories" exist:
|
||||
| contextlevel | reference | questioncategory | name | idnumber |
|
||||
| Course | C1 | Top | top | |
|
||||
| Course | C1 | top | Used category | c1used |
|
||||
And I navigate to "Question bank > Categories" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Name | Sub used category |
|
||||
| Parent category | Used category |
|
||||
| Category info | Created as a test |
|
||||
| ID number | c1used |
|
||||
And I press "Add category"
|
||||
# Standard warning.
|
||||
Then I should see "This ID number is already in use"
|
||||
# Correction to a unique idnumber for the context.
|
||||
And I set the field "ID number" to "c1unused"
|
||||
And I press "Add category"
|
||||
Then I should see "Sub used category ID number c1unused (0)"
|
||||
And I should see "Created as a test" in the "Sub used category" "list_item"
|
||||
|
||||
Scenario: A question category can be edited and saved without changing the idnumber
|
||||
When the following "question categories" exist:
|
||||
| contextlevel | reference | questioncategory | name | idnumber |
|
||||
| Course | C1 | Top | top | |
|
||||
| Course | C1 | top | Used category | c1used |
|
||||
And I navigate to "Question bank > Categories" in current page administration
|
||||
And I click on "Edit this category" "link" in the "Used category" "list_item"
|
||||
And I press "Save changes"
|
||||
Then I should not see "This ID number is already in use"
|
||||
|
||||
Scenario: A question can only have a unique idnumber within a category
|
||||
When the following "question categories" exist:
|
||||
| contextlevel | reference | questioncategory | name | idnumber |
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Events tests.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2019 the Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/question/editlib.php');
|
||||
require_once($CFG->dirroot . '/question/category_class.php');
|
||||
|
||||
class core_question_category_class_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* @var question_category_object used in the tests.
|
||||
*/
|
||||
protected $qcobject;
|
||||
|
||||
/**
|
||||
* @var context a context to use.
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var stdClass top category in context.
|
||||
*/
|
||||
protected $topcat;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
self::setAdminUser();
|
||||
$this->resetAfterTest();
|
||||
$this->context = context_course::instance(SITEID);
|
||||
$contexts = new question_edit_contexts($this->context);
|
||||
$this->topcat = question_get_top_category($this->context->id, true);
|
||||
$this->qcobject = new question_category_object(null,
|
||||
new moodle_url('/question/category.php', ['courseid' => SITEID]),
|
||||
$contexts->having_one_edit_tab_cap('categories'), 0, null, 0,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a category.
|
||||
*/
|
||||
public function test_add_category_no_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New category', '', true, FORMAT_HTML, ''); // No idnumber passed as '' to match form data.
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New category', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a category with a tricky idnumber.
|
||||
*/
|
||||
public function test_add_category_set_idnumber_0() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New category', '', true, FORMAT_HTML, '0');
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New category', $newcat->name);
|
||||
$this->assertSame('0', $newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to add a category with duplicate idnumber blanks it.
|
||||
* (In reality, this would probably get caught by form validation.)
|
||||
*/
|
||||
public function test_add_category_try_to_set_duplicate_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Existing category', '', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New category', '', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New category', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a category.
|
||||
*/
|
||||
public function test_update_category() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Old name', 'Description', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', 'New description', FORMAT_HTML, '0', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertSame('0', $newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a category to remove the idnumber.
|
||||
*/
|
||||
public function test_update_category_removing_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Old name', 'Description', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', 'New description', FORMAT_HTML, '', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a category without changing the idnumber.
|
||||
*/
|
||||
public function test_update_category_dont_change_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Old name', 'Description', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', 'New description', FORMAT_HTML, 'frog', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertSame('frog', $newcat->idnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to update a category so its idnumber duplicates idnumber blanks it.
|
||||
* (In reality, this would probably get caught by form validation.)
|
||||
*/
|
||||
public function test_update_category_try_to_set_duplicate_idnumber() {
|
||||
global $DB;
|
||||
|
||||
$this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'Existing category', '', true, FORMAT_HTML, 'toad');
|
||||
$id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'old name', '', true, FORMAT_HTML, 'frog');
|
||||
|
||||
$this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid,
|
||||
'New name', '', FORMAT_HTML, 'toad', false);
|
||||
|
||||
$newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST);
|
||||
$this->assertSame('New name', $newcat->name);
|
||||
$this->assertNull($newcat->idnumber);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,8 @@ defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/question/editlib.php');
|
||||
require_once($CFG->dirroot . '/question/category_class.php');
|
||||
|
||||
use qbank_managecategories\question_category_object;
|
||||
|
||||
class core_question_events_testcase extends advanced_testcase {
|
||||
|
||||
@@ -38,166 +39,6 @@ class core_question_events_testcase extends advanced_testcase {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category created event.
|
||||
*/
|
||||
public function test_question_category_created() {
|
||||
$this->setAdminUser();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
$contexts = new question_edit_contexts(context_module::instance($quiz->cmid));
|
||||
|
||||
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
|
||||
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
|
||||
|
||||
$qcobject = new question_category_object(
|
||||
1,
|
||||
new moodle_url('/mod/quiz/edit.php', ['cmid' => $quiz->cmid]),
|
||||
$contexts->having_one_edit_tab_cap('categories'),
|
||||
$defaultcategoryobj->id,
|
||||
$defaultcategory,
|
||||
null,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$categoryid = $qcobject->add_category($defaultcategory, 'newcategory', '', true);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_created', $event);
|
||||
$this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
|
||||
$expected = [$course->id, 'quiz', 'addcategory', 'view.php?id=' . $quiz->cmid , $categoryid, $quiz->cmid];
|
||||
$this->assertEventLegacyLogData($expected, $event);
|
||||
$this->assertEventContextNotUsed($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category deleted event.
|
||||
*/
|
||||
public function test_question_category_deleted() {
|
||||
$this->setAdminUser();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
$contexts = new question_edit_contexts(context_module::instance($quiz->cmid));
|
||||
|
||||
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
|
||||
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
|
||||
|
||||
$qcobject = new question_category_object(
|
||||
1,
|
||||
new moodle_url('/mod/quiz/edit.php', ['cmid' => $quiz->cmid]),
|
||||
$contexts->having_one_edit_tab_cap('categories'),
|
||||
$defaultcategoryobj->id,
|
||||
$defaultcategory,
|
||||
null,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
|
||||
// Create the category.
|
||||
$categoryid = $qcobject->add_category($defaultcategory, 'newcategory', '', true);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$qcobject->delete_category($categoryid);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_deleted', $event);
|
||||
$this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
|
||||
$this->assertEquals($categoryid, $event->objectid);
|
||||
$this->assertDebuggingNotCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category updated event.
|
||||
*/
|
||||
public function test_question_category_updated() {
|
||||
$this->setAdminUser();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
$contexts = new question_edit_contexts(context_module::instance($quiz->cmid));
|
||||
|
||||
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
|
||||
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
|
||||
|
||||
$qcobject = new question_category_object(
|
||||
1,
|
||||
new moodle_url('/mod/quiz/edit.php', ['cmid' => $quiz->cmid]),
|
||||
$contexts->having_one_edit_tab_cap('categories'),
|
||||
$defaultcategoryobj->id,
|
||||
$defaultcategory,
|
||||
null,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
|
||||
// Create the category.
|
||||
$categoryid = $qcobject->add_category($defaultcategory, 'newcategory', '', true);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$qcobject->update_category($categoryid, $defaultcategory, 'updatedcategory', '', FORMAT_HTML, '', false);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_updated', $event);
|
||||
$this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
|
||||
$this->assertEquals($categoryid, $event->objectid);
|
||||
$this->assertDebuggingNotCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the question category viewed event.
|
||||
* There is no external API for viewing the category, so the unit test will simply
|
||||
* create and trigger the event and ensure data is returned as expected.
|
||||
*/
|
||||
public function test_question_category_viewed() {
|
||||
|
||||
$this->setAdminUser();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
$contexts = new question_edit_contexts(context_module::instance($quiz->cmid));
|
||||
|
||||
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
|
||||
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
|
||||
|
||||
$qcobject = new question_category_object(
|
||||
1,
|
||||
new moodle_url('/mod/quiz/edit.php', ['cmid' => $quiz->cmid]),
|
||||
$contexts->having_one_edit_tab_cap('categories'),
|
||||
$defaultcategoryobj->id,
|
||||
$defaultcategory,
|
||||
null,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
|
||||
// Create the category.
|
||||
$categoryid = $qcobject->add_category($defaultcategory, 'newcategory', '', true);
|
||||
|
||||
// Log the view of this category.
|
||||
$category = new stdClass();
|
||||
$category->id = $categoryid;
|
||||
$context = context_module::instance($quiz->cmid);
|
||||
$event = \core\event\question_category_viewed::create_from_question_category_instance($category, $context);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$event->trigger();
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_category_viewed', $event);
|
||||
$this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
|
||||
$this->assertEquals($categoryid, $event->objectid);
|
||||
$this->assertDebuggingNotCalled();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the questions imported event.
|
||||
* There is no easy way to trigger this event using the API, so the unit test will simply
|
||||
|
||||
Reference in New Issue
Block a user