mirror of
https://github.com/moodle/moodle.git
synced 2025-04-14 04:52:36 +02:00
Merge branch 'master_MDL-71585-managecategories' of https://github.com/catalyst/moodle-MDL-70329
This commit is contained in:
commit
3b903ae45d
@ -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']]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1944,6 +1944,7 @@ class core_plugin_manager {
|
||||
'exporttoxml',
|
||||
'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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,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 <ryan@moodle.com>\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 <ryan@moodle.com>\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,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 <ryan@moodle.com>\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 <ryan@moodle.com>\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 me@jamiep.org
|
||||
* @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);
|
||||
}
|
||||
}
|
373
question/bank/managecategories/classes/helper.php
Normal file
373
question/bank/managecategories/classes/helper.php
Normal file
@ -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 <guillermogomez@catalyst-au.net>
|
||||
* @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;
|
||||
}
|
||||
}
|
42
question/bank/managecategories/classes/navigation.php
Normal file
42
question/bank/managecategories/classes/navigation.php
Normal file
@ -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 <guillermogomez@catalyst-au.net>
|
||||
* @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');
|
||||
}
|
||||
}
|
35
question/bank/managecategories/classes/plugin_feature.php
Normal file
35
question/bank/managecategories/classes/plugin_feature.php
Normal file
@ -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 <safatshahin@catalyst-au.net>
|
||||
* @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();
|
||||
}
|
||||
}
|
38
question/bank/managecategories/classes/privacy/provider.php
Normal file
38
question/bank/managecategories/classes/privacy/provider.php
Normal file
@ -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 <guillermogomez@catalyst-au.net>
|
||||
* @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 <guillermogomez@catalyst-au.net>
|
||||
* @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.';
|
57
question/bank/managecategories/templates/listitem.mustache
Normal file
57
question/bank/managecategories/templates/listitem.mustache
Normal file
@ -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"
|
228
question/bank/managecategories/tests/helper_test.php
Normal file
228
question/bank/managecategories/tests/helper_test.php
Normal file
@ -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 <guillermogomez@catalyst-au.net>
|
||||
* @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 <guillermogomez@catalyst-au.net>
|
||||
* @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();
|
||||
|
||||
}
|
||||
}
|
31
question/bank/managecategories/version.php
Normal file
31
question/bank/managecategories/version.php
Normal file
@ -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 <guillermogomez@catalyst-au.net>
|
||||
* @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 me@jamiep.org
|
||||
* @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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user