From 68beb75851f77a60d25b52e496872700b46b8349 Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Tue, 20 Feb 2018 16:02:06 +0800 Subject: [PATCH] MDL-61363 question: update tags on question context change --- lib/questionlib.php | 128 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/lib/questionlib.php b/lib/questionlib.php index 4266038f382..eb99d4f383a 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -537,6 +537,120 @@ function question_delete_activity($cm, $feedback=true) { return true; } +/** + * 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 + */ +function question_move_question_tags_to_new_context(array $questions, context $newcontext) { + // 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 = []; + $instancesfornewcontext = []; + $newcontextparentids = $newcontext->get_parent_context_ids(); + $questionids = array_map(function($question) { + return $question->id; + }, $questions); + $questionstagobjects = core_tag_tag::get_items_tags('core_question', 'question', $questionids); + + foreach ($questions as $question) { + $tagobjects = $questionstagobjects[$question->id]; + + foreach ($tagobjects as $tagobject) { + $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 + // ignore it. + 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; + } + } + } + + 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); + } +} + /** * This function should be considered private to the question bank, it is called from * question/editlib.php question/contextmoveq.php and a few similar places to to the @@ -573,8 +687,8 @@ function question_move_questions_to_category($questionids, $newcategoryid) { $DB->set_field_select('question', 'category', $newcategoryid, "parent $questionidcondition", $params); - // Update the contextid for any tag instances that may exist for these questions. - core_tag_tag::change_items_context('core_question', 'question', $questionids, $newcontextid); + $newcontext = context::instance_by_id($newcontextid); + question_move_question_tags_to_new_context($questions, $newcontext); // TODO Deal with datasets. @@ -597,6 +711,7 @@ function question_move_questions_to_category($questionids, $newcategoryid) { function question_move_category_to_context($categoryid, $oldcontextid, $newcontextid) { global $DB; + $questions = []; $questionids = $DB->get_records_menu('question', array('category' => $categoryid), '', 'id,qtype'); foreach ($questionids as $questionid => $qtype) { @@ -604,10 +719,15 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte $questionid, $oldcontextid, $newcontextid); // Purge this question from the cache. question_bank::notify_question_edited($questionid); + + $questions[] = (object) [ + 'id' => $questionid, + 'contextid' => $oldcontextid + ]; } - core_tag_tag::change_items_context('core_question', 'question', - array_keys($questionids), $newcontextid); + $newcontext = context::instance_by_id($newcontextid); + question_move_question_tags_to_new_context($questions, $newcontext); $subcatids = $DB->get_records_menu('question_categories', array('parent' => $categoryid), '', 'id,1');