MDL-71378 core_question & mod_quiz: Refactor deprecated contexts
This commit is contained in:
+54
-171
@@ -438,57 +438,6 @@ function question_delete_context($contextid): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* All question categories and their questions are deleted for this course.
|
||||
*
|
||||
* @param stdClass $course an object representing the activity
|
||||
* @param bool $notused this argument is not used any more. Kept for backwards compatibility.
|
||||
* @return bool always true.
|
||||
*/
|
||||
function question_delete_course($course, $notused = false): bool {
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
question_delete_context($coursecontext->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category is about to be deleted,
|
||||
* 1/ All question categories and their questions are deleted for this course category.
|
||||
* 2/ All questions are moved to new category
|
||||
*
|
||||
* @param stdClass|core_course_category $category course category object
|
||||
* @param stdClass|core_course_category $newcategory empty means everything deleted, otherwise id of
|
||||
* category where content moved
|
||||
* @param bool $notused this argument is no longer used. Kept for backwards compatibility.
|
||||
* @return boolean
|
||||
*/
|
||||
function question_delete_course_category($category, $newcategory, $notused=false): bool {
|
||||
global $DB;
|
||||
|
||||
$context = context_coursecat::instance($category->id);
|
||||
if (empty($newcategory)) {
|
||||
question_delete_context($context->id);
|
||||
|
||||
} else {
|
||||
// Move question categories to the new context.
|
||||
if (!$newcontext = context_coursecat::instance($newcategory->id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only move question categories if there is any question category at all!
|
||||
if ($topcategory = question_get_top_category($context->id)) {
|
||||
$newtopcategory = question_get_top_category($newcontext->id, true);
|
||||
|
||||
question_move_category_to_context($topcategory->id, $context->id, $newcontext->id);
|
||||
$DB->set_field('question_categories', 'parent', $newtopcategory->id, ['parent' => $topcategory->id]);
|
||||
// Now delete the top category.
|
||||
$DB->delete_records('question_categories', ['id' => $topcategory->id]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new category to save the questions in use.
|
||||
*
|
||||
@@ -502,6 +451,11 @@ function question_delete_course_category($category, $newcategory, $notused=false
|
||||
function question_save_from_deletion($questionids, $newcontextid, $oldplace, $newcategory = null) {
|
||||
global $DB;
|
||||
|
||||
$newcontext = context::instance_by_id($newcontextid);
|
||||
if ($newcontext->contextlevel !== CONTEXT_MODULE) {
|
||||
throw new moodle_exception("Invalid contextlevel: {$newcontext->contextlevel} for \$newcontextid {$newcontextid}");
|
||||
}
|
||||
|
||||
// Make a category in the parent context to move the questions to.
|
||||
if (is_null($newcategory)) {
|
||||
$newcategory = new stdClass();
|
||||
@@ -539,35 +493,17 @@ function question_delete_activity($cm, $notused = false): bool {
|
||||
* This function will handle moving all tag instances to a new context for a
|
||||
* given list of questions.
|
||||
*
|
||||
* Questions can be tagged in up to two contexts:
|
||||
* 1.) The context the question exists in.
|
||||
* 2.) The course context (if the question context is a higher context.
|
||||
* E.g. course category context or system context.
|
||||
*
|
||||
* This means a question that exists in a higher context (e.g. course cat or
|
||||
* system context) may have multiple groups of tags in any number of child
|
||||
* course contexts.
|
||||
*
|
||||
* Questions in the course category context can be move "down" a context level
|
||||
* into one of their child course contexts or activity contexts which affects the
|
||||
* availability of that question in other courses / activities.
|
||||
*
|
||||
* In this case it makes the questions no longer available in the other course or
|
||||
* activity contexts so we need to make sure that the tag instances in those other
|
||||
* contexts are removed.
|
||||
*
|
||||
* @param stdClass[] $questions The list of question being moved (must include
|
||||
* the id and contextid)
|
||||
* @param context $newcontext The Moodle context the questions are being moved to
|
||||
* @param context $newcontext The Moodle context the questions are being moved to, must be module context.
|
||||
*/
|
||||
function question_move_question_tags_to_new_context(array $questions, context $newcontext): void {
|
||||
// If the questions are moving to a new course/activity context then we need to
|
||||
// find any existing tag instances from any unavailable course contexts and
|
||||
// delete them because they will no longer be applicable (we don't support
|
||||
// tagging questions across courses).
|
||||
$instancestodelete = [];
|
||||
|
||||
if ($newcontext->contextlevel !== CONTEXT_MODULE) {
|
||||
debugging("Invalid contextlevel: {$newcontext->contextlevel}", DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$instancesfornewcontext = [];
|
||||
$newcontextparentids = $newcontext->get_parent_context_ids();
|
||||
$questionids = array_map(function($question) {
|
||||
return $question->id;
|
||||
}, $questions);
|
||||
@@ -580,7 +516,6 @@ function question_move_question_tags_to_new_context(array $questions, context $n
|
||||
$tagid = $tagobject->taginstanceid;
|
||||
$tagcontextid = $tagobject->taginstancecontextid;
|
||||
$istaginnewcontext = $tagcontextid == $newcontext->id;
|
||||
$istaginquestioncontext = $tagcontextid == $question->contextid;
|
||||
|
||||
if ($istaginnewcontext) {
|
||||
// This tag instance is already in the correct context so we can
|
||||
@@ -588,61 +523,10 @@ function question_move_question_tags_to_new_context(array $questions, context $n
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($istaginquestioncontext) {
|
||||
// This tag instance is in the question context so it needs to be
|
||||
// updated.
|
||||
$instancesfornewcontext[] = $tagid;
|
||||
continue;
|
||||
}
|
||||
|
||||
// These tag instances are in neither the new context nor the
|
||||
// question context so we need to determine what to do based on
|
||||
// the context they are in and the new question context.
|
||||
$tagcontext = context::instance_by_id($tagcontextid);
|
||||
$tagcoursecontext = $tagcontext->get_course_context(false);
|
||||
// The tag is in a course context if get_course_context() returns
|
||||
// itself.
|
||||
$istaginstancecontextcourse = !empty($tagcoursecontext)
|
||||
&& $tagcontext->id == $tagcoursecontext->id;
|
||||
|
||||
if ($istaginstancecontextcourse) {
|
||||
// If the tag instance is in a course context we need to add some
|
||||
// special handling.
|
||||
$tagcontextparentids = $tagcontext->get_parent_context_ids();
|
||||
$isnewcontextaparent = in_array($newcontext->id, $tagcontextparentids);
|
||||
$isnewcontextachild = in_array($tagcontext->id, $newcontextparentids);
|
||||
|
||||
if ($isnewcontextaparent) {
|
||||
// If the tag instance is a course context tag and the new
|
||||
// context is still a parent context to the tag context then
|
||||
// we can leave this tag where it is.
|
||||
continue;
|
||||
} else if ($isnewcontextachild) {
|
||||
// If the new context is a child context (e.g. activity) of this
|
||||
// tag instance then we should move all of this tag instance
|
||||
// down into the activity context along with the question.
|
||||
$instancesfornewcontext[] = $tagid;
|
||||
} else {
|
||||
// If the tag is in a course context that is no longer a parent
|
||||
// or child of the new context then this tag instance should be
|
||||
// removed.
|
||||
$instancestodelete[] = $tagid;
|
||||
}
|
||||
} else {
|
||||
// This is a catch all for any tag instances not in the question
|
||||
// context or a course context. These tag instances should be
|
||||
// updated to the new context id. This will clean up old invalid
|
||||
// data.
|
||||
$instancesfornewcontext[] = $tagid;
|
||||
}
|
||||
$instancesfornewcontext[] = $tagid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($instancestodelete)) {
|
||||
// Delete any course context tags that may no longer be valid.
|
||||
core_tag_tag::delete_instances_by_id($instancestodelete);
|
||||
}
|
||||
|
||||
if (!empty($instancesfornewcontext)) {
|
||||
// Update the tag instances to the new context id.
|
||||
core_tag_tag::change_instances_context($instancesfornewcontext, $newcontext);
|
||||
@@ -807,6 +691,11 @@ function move_question_set_references(int $oldcategoryid, int $newcatgoryid,
|
||||
function question_move_category_to_context($categoryid, $oldcontextid, $newcontextid): void {
|
||||
global $DB;
|
||||
|
||||
$newcontext = context::instance_by_id($newcontextid);
|
||||
if ($newcontext->contextlevel !== CONTEXT_MODULE) {
|
||||
debugging("Invalid contextlevel: {$newcontext->contextlevel}, must use CONTEXT_MODULE", DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$questions = [];
|
||||
$sql = "SELECT q.id, q.qtype
|
||||
FROM {question} q
|
||||
@@ -826,7 +715,6 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte
|
||||
];
|
||||
}
|
||||
|
||||
$newcontext = context::instance_by_id($newcontextid);
|
||||
question_move_question_tags_to_new_context($questions, $newcontext);
|
||||
|
||||
$subcatids = $DB->get_records_menu('question_categories', ['parent' => $categoryid], '', 'id,1');
|
||||
@@ -935,9 +823,14 @@ function question_load_questions($questionids, $extrafields = '', $join = '') {
|
||||
* @param object $question the question to tidy.
|
||||
* @param stdClass $category The question_categories record for the given $question.
|
||||
* @param \core_tag_tag[]|null $tagobjects The tags for the given $question.
|
||||
* @param stdClass[]|null $filtercourses The courses to filter the course tags by.
|
||||
* @param stdClass[]|null $filtercourses deprecated argument and should not be used
|
||||
*/
|
||||
function _tidy_question($question, $category, ?array $tagobjects = null, ?array $filtercourses = null): void {
|
||||
|
||||
if ($filtercourses !== null) {
|
||||
debugging("Filtercourses is a deprecated argument in " . __FUNCTION__, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
// Convert numeric fields to float. This prevents these being displayed as 1.0000000.
|
||||
$question->defaultmark += 0;
|
||||
$question->penalty += 0;
|
||||
@@ -953,8 +846,6 @@ function _tidy_question($question, $category, ?array $tagobjects = null, ?array
|
||||
if (!is_null($tagobjects)) {
|
||||
$categorycontext = context::instance_by_id($category->contextid);
|
||||
$sortedtagobjects = question_sort_tags($tagobjects, $categorycontext, $filtercourses);
|
||||
$question->coursetagobjects = $sortedtagobjects->coursetagobjects;
|
||||
$question->coursetags = $sortedtagobjects->coursetags;
|
||||
$question->tagobjects = $sortedtagobjects->tagobjects;
|
||||
$question->tags = $sortedtagobjects->tags;
|
||||
}
|
||||
@@ -978,12 +869,16 @@ function _tidy_question($question, $category, ?array $tagobjects = null, ?array
|
||||
* @param mixed $questions Either an array of question objects to be updated
|
||||
* or just a single question object
|
||||
* @param bool $loadtags load the question tags from the tags table. Optional, default false.
|
||||
* @param stdClass[] $filtercourses The courses to filter the course tags by.
|
||||
* @param stdClass[] $filtercourses deprecated argument and should not be used
|
||||
* @return bool Indicates success or failure.
|
||||
*/
|
||||
function get_question_options(&$questions, $loadtags = false, $filtercourses = null) {
|
||||
global $DB;
|
||||
|
||||
if ($filtercourses !== null) {
|
||||
debugging("Filtercourses is a deprecated argument in " . __FUNCTION__, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$questionlist = is_array($questions) ? $questions : [$questions];
|
||||
$categoryids = [];
|
||||
$questionids = [];
|
||||
@@ -1037,55 +932,24 @@ function get_question_options(&$questions, $loadtags = false, $filtercourses = n
|
||||
*
|
||||
* @param \core_tag_tag[] $tagobjects The tags for the given $question.
|
||||
* @param stdClass $categorycontext The question categories context.
|
||||
* @param stdClass[]|null $filtercourses The courses to filter the course tags by.
|
||||
* @param stdClass[]|null $filtercourses deprecated argument and should not be used.
|
||||
* @return stdClass $sortedtagobjects Sorted tag objects.
|
||||
*/
|
||||
function question_sort_tags($tagobjects, $categorycontext, $filtercourses = null): stdClass {
|
||||
|
||||
// Questions can have two sets of tag instances. One set at the
|
||||
// course context level and another at the context the question
|
||||
// belongs to (e.g. course category, system etc).
|
||||
if ($filtercourses !== null) {
|
||||
debugging("Filtercourses is a deprecated argument in " . __FUNCTION__, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$sortedtagobjects = new stdClass();
|
||||
$sortedtagobjects->coursetagobjects = [];
|
||||
$sortedtagobjects->coursetags = [];
|
||||
$sortedtagobjects->tagobjects = [];
|
||||
$sortedtagobjects->tags = [];
|
||||
$taginstanceidstonormalise = [];
|
||||
$filtercoursecontextids = [];
|
||||
$hasfiltercourses = !empty($filtercourses);
|
||||
|
||||
if ($hasfiltercourses) {
|
||||
// If we're being asked to filter the course tags by a set of courses
|
||||
// then get the context ids to filter below.
|
||||
$filtercoursecontextids = array_map(function($course) {
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
return $coursecontext->id;
|
||||
}, $filtercourses);
|
||||
}
|
||||
|
||||
foreach ($tagobjects as $tagobject) {
|
||||
$tagcontextid = $tagobject->taginstancecontextid;
|
||||
$tagcontext = context::instance_by_id($tagcontextid);
|
||||
$tagcoursecontext = $tagcontext->get_course_context(false);
|
||||
// This is a course tag if the tag context is a course context which
|
||||
// doesn't match the question's context. Any tag in the question context
|
||||
// is not considered a course tag, it belongs to the question.
|
||||
$iscoursetag = $tagcoursecontext
|
||||
&& $tagcontext->id == $tagcoursecontext->id
|
||||
&& $tagcontext->id != $categorycontext->id;
|
||||
|
||||
if ($iscoursetag) {
|
||||
// Any tag instance in a course context level is considered a course tag.
|
||||
if (!$hasfiltercourses || in_array($tagcontextid, $filtercoursecontextids)) {
|
||||
// Add the tag to the list of course tags if we aren't being
|
||||
// asked to filter or if this tag is in the list of courses
|
||||
// we're being asked to filter by.
|
||||
$sortedtagobjects->coursetagobjects[] = $tagobject;
|
||||
$sortedtagobjects->coursetags[$tagobject->id] = $tagobject->get_display_name();
|
||||
}
|
||||
} else {
|
||||
// All non course context level tag instances or tags in the question
|
||||
// context belong to the context that the question was created in.
|
||||
// All tag instances belong to the context that the question was created in.
|
||||
$sortedtagobjects->tagobjects[] = $tagobject;
|
||||
$sortedtagobjects->tags[$tagobject->id] = $tagobject->get_display_name();
|
||||
|
||||
@@ -1100,7 +964,7 @@ function question_sort_tags($tagobjects, $categorycontext, $filtercourses = null
|
||||
// happen below.
|
||||
$tagobject->taginstancecontextid = $categorycontext->id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($taginstanceidstonormalise)) {
|
||||
@@ -1232,6 +1096,15 @@ function question_get_top_category($contextid, $create = false) {
|
||||
global $DB;
|
||||
$category = $DB->get_record('question_categories', ['contextid' => $contextid, 'parent' => 0]);
|
||||
|
||||
$context = context::instance_by_id($contextid);
|
||||
if ($context->contextlevel !== CONTEXT_MODULE) {
|
||||
debugging(
|
||||
"Invalid context level: {$context->contextlevel} for question_get_top_category, must be CONTEXT_MODULE",
|
||||
DEBUG_DEVELOPER
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$category && $create) {
|
||||
// We need to make one.
|
||||
$category = new stdClass();
|
||||
@@ -1485,6 +1358,15 @@ function question_edit_url($context) {
|
||||
if (!has_any_capability(question_get_question_capabilities(), $context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($context->contextlevel !== CONTEXT_MODULE) {
|
||||
debugging(
|
||||
"Invalid contextlevel: {$context->contextlevel} provided for question_edit_url, must be CONTEXT_MODULE",
|
||||
DEBUG_DEVELOPER
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$baseurl = $CFG->wwwroot . '/question/edit.php?';
|
||||
$defaultcategory = question_get_default_category($context->id, true);
|
||||
if ($defaultcategory) {
|
||||
@@ -1503,6 +1385,7 @@ function question_edit_url($context) {
|
||||
return $baseurl . 'cmid=' . $context->instanceid;
|
||||
}
|
||||
|
||||
return $baseurl . 'cmid=' . $context->instanceid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user