Commit fb10b36c authored by Guillermo Gomez's avatar Guillermo Gomez
Browse files

MDL-71585 qbank_managecategories: Add managecategories to core

This implementation will introduce a qbank plugin "managecategories"
which will add the question categories feature in the question bank view
by replacing the core classes. Having this plugin will give users
the flexibility of enabling or disabling the category tab.
parent 443a980a
......@@ -60,7 +60,7 @@ abstract class question_base extends base {
['courseid' => $this->courseid, 'cat' => $cat, 'lastchanged' => $this->objectid]);
}
// Lets try viewing from the frontpage for contexts above course.
return new \moodle_url('/question/category.php',
return new \moodle_url('/question/bank/managecategories/category.php',
['courseid' => SITEID, 'edit' => $this->other['categoryid'], 'lastchanged' => $this->objectid]);
}
......
......@@ -58,7 +58,7 @@ abstract class question_category_base extends base {
return new \moodle_url('/question/edit.php', ['courseid' => $this->courseid, 'cat' => $cat]);
}
// Lets try viewing from the frontpage for contexts above course.
return new \moodle_url('/question/category.php', ['courseid' => SITEID, 'edit' => $this->objectid]);
return new \moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID, 'edit' => $this->objectid]);
}
/**
......
......@@ -87,7 +87,7 @@ class question_moved extends question_base {
['courseid' => $this->courseid, 'cat' => $cat, 'lastchanged' => $this->objectid]);
}
// Lets try viewing from the frontpage for contexts above course.
return new \moodle_url('/question/category.php',
return new \moodle_url('/question/bank/managecategories/category.php',
['courseid' => SITEID, 'edit' => $this->other['newcategoryid'], 'lastchanged' => $this->objectid]);
}
......
......@@ -83,7 +83,8 @@ class questions_exported extends question_base {
}
return new \moodle_url('/question/edit.php', ['courseid' => $this->courseid, 'cat' => $cat]);
}
return new \moodle_url('/question/category.php', ['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
return new \moodle_url('/question/bank/managecategories/category.php',
['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
}
/**
......
......@@ -83,7 +83,8 @@ class questions_imported extends question_base {
}
return new \moodle_url('/question/edit.php', ['courseid' => $this->courseid, 'cat' => $cat]);
}
return new \moodle_url('/question/category.php', ['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
return new \moodle_url('/question/bank/managecategories/category.php',
['courseid' => SITEID, 'edit' => $this->other['categoryid']]);
}
/**
......
......@@ -1943,6 +1943,7 @@ class core_plugin_manager {
'editquestion',
'exportquestions',
'importquestions',
'managecategories',
'viewcreator',
'viewquestionname',
'viewquestiontext',
......
......@@ -26,6 +26,7 @@
*/
global $CFG;
use qbank_managecategories\helper;
require_once("$CFG->libdir/form/selectgroups.php");
require_once("$CFG->libdir/questionlib.php");
......@@ -59,8 +60,8 @@ class MoodleQuickForm_questioncategory extends MoodleQuickForm_selectgroups {
if (is_array($options)) {
$this->_options = $options + $this->_options;
$this->loadArrayOptGroups(
question_category_options($this->_options['contexts'], $this->_options['top'], $this->_options['currentcat'],
false, $this->_options['nochildrenof'], false));
helper::question_category_options($this->_options['contexts'], $this->_options['top'],
$this->_options['currentcat'], false, $this->_options['nochildrenof'], false));
}
}
......
......@@ -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');
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -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.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment