diff --git a/question/category.php b/question/category.php index c740522a63a2a..952d4315a00cb 100644 --- a/question/category.php +++ b/question/category.php @@ -97,7 +97,7 @@ } question_remove_stale_questions_from_category($param->delete); - $questionstomove = $DB->count_records("question", array("category" => $param->delete)); + $questionstomove = count($qcobject->get_real_question_ids_in_category($param->delete)); // Second pass, if we still have questions to move, setup the form. if ($questionstomove) { diff --git a/question/category_class.php b/question/category_class.php index 1713cadea5cf7..61123d7c89fea 100644 --- a/question/category_class.php +++ b/question/category_class.php @@ -435,10 +435,8 @@ public function display_move_form($questionsincategory, $category){ } public function move_questions($oldcat, $newcat){ - global $DB; - $questionids = $DB->get_records_select_menu('question', - 'category = ? AND (parent = 0 OR parent = id)', array($oldcat), '', 'id,1'); - question_move_questions_to_category(array_keys($questionids), $newcat); + $questionids = $this->get_real_question_ids_in_category($oldcat); + question_move_questions_to_category($questionids, $newcat); } /** @@ -606,4 +604,21 @@ public function update_category($updateid, $newparent, $newname, $newinfo, $newi redirect($this->pageurl); // Always redirect after successful action. } } + + /** + * Returns ids of the question in the given question category. + * + * This method only returns the real question. It does not include + * subquestions of question types like multianswer. + * + * @param int $categoryid id of the category. + * @return int[] array of question ids. + */ + public function get_real_question_ids_in_category(int $categoryid): array { + global $DB; + $select = 'category = :categoryid AND (parent = 0 OR parent = id)'; + $params = ['categoryid' => $categoryid]; + $questionids = $DB->get_records_select('question', $select, $params); + return array_keys($questionids); + } } diff --git a/question/tests/behat/question_categories.feature b/question/tests/behat/question_categories.feature index fbcba7531f1c6..cb792938fb19a 100644 --- a/question/tests/behat/question_categories.feature +++ b/question/tests/behat/question_categories.feature @@ -86,3 +86,20 @@ Feature: A teacher can put questions in categories in the question bank And the field "Select a category" matches value "      Subcategory (1)" And the "Select a category" select box should contain "Used category" And the "Select a category" select box should not contain "Used category (1)" + + @_file_upload + Scenario: Multi answer questions with their child questions can be moved to another category when the current category is deleted + When I navigate to "Question bank > Import" in current page administration + And I set the field "id_format_xml" to "1" + And I upload "question/format/xml/tests/fixtures/multianswer.xml" file to "Import" filemanager + And I press "id_submitbutton" + And I press "Continue" + And I am on "Course 1" course homepage + And I navigate to "Question bank > Categories" in current page administration + And I click on "Delete" "link" in the "Default for Test images in backup" "list_item" + And I should see "The category 'Default for Test images in backup' contains 1 questions" + And I select "Used category" from the "Category" singleselect + And I press "Save in category" + Then I should not see "Default for Test images in backup" + And I follow "Add category" + And I should see "Used category (2)" diff --git a/question/tests/category_class_test.php b/question/tests/category_class_test.php index adbe41cea3af5..e5931a72d11af 100644 --- a/question/tests/category_class_test.php +++ b/question/tests/category_class_test.php @@ -175,4 +175,68 @@ public function test_update_category_try_to_set_duplicate_idnumber() { $this->assertSame('New name', $newcat->name); $this->assertNull($newcat->idnumber); } + + /** + * Test that get_real_question_ids_in_category() returns question id + * of a shortanswer question in a category. + * + * @covers ::get_real_question_ids_in_category + */ + public function test_get_real_question_ids_in_category_shortanswer() { + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $categoryid = $this->topcat->id; + + // Short answer question is made of one question. + $shortanswer = $generator->create_question('shortanswer', null, ['category' => $categoryid]); + $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); + $this->assertCount(1, $questionids); + $this->assertContains($shortanswer->id, $questionids); + } + + /** + * Test that get_real_question_ids_in_category() returns question id + * of a multianswer question in a category. + * + * @covers ::get_real_question_ids_in_category + */ + public function test_get_real_question_ids_in_category_multianswer() { + global $DB; + $countq = $DB->count_records('question'); + + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $categoryid = $this->topcat->id; + + // Multi answer question is made of one parent and two child questions. + $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); + $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); + $this->assertCount(1, $questionids); + $this->assertContains($multianswer->id, $questionids); + $this->assertEquals(3, $DB->count_records('question') - $countq); + } + + /** + * Test that get_real_question_ids_in_category() returns question id + * of a multianswer question in a category even if their child questions are + * linked to a category that doesn't exist. + * + * @covers ::get_real_question_ids_in_category + */ + public function test_get_real_question_ids_in_category_multianswer_bad_data() { + global $DB; + $countq = $DB->count_records('question'); + + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $categoryid = $this->topcat->id; + + // Multi answer question is made of one parent and two child questions. + $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); + + // Update category id for child questions to a category that doesn't exist. + $DB->set_field_select('question', 'category', 123456, 'id <> :id', ['id' => $multianswer->id]); + + $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); + $this->assertCount(1, $questionids); + $this->assertContains($multianswer->id, $questionids); + $this->assertEquals(3, $DB->count_records('question') - $countq); + } }