MDL-87356 qtype_random: orphaned random questions should be deleted
This patch reinstates the task from MDL-63260 and MDL-66273.
This commit is contained in:
@@ -5289,6 +5289,15 @@ class restore_create_categories_and_questions extends restore_structure_step {
|
||||
$this->set_mapping('question_bank_entry', $this->latestqbe->oldid, $this->latestqbe->newid);
|
||||
}
|
||||
|
||||
if (
|
||||
($data->qtype === 'random')
|
||||
&& ($this->latestversion->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN)
|
||||
) {
|
||||
// Ensure that this newly created question is considered by
|
||||
// \qtype_random\task\remove_unused_questions.
|
||||
$this->latestversion->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT;
|
||||
}
|
||||
|
||||
// Now store the question.
|
||||
$newitemid = $DB->insert_record('question', $data);
|
||||
$this->set_mapping('question', $oldid, $newitemid);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* A scheduled task to remove unneeded random questions.
|
||||
*
|
||||
* @package qtype_random
|
||||
* @category task
|
||||
* @copyright 2018 Bo Pierce <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace qtype_random\task;
|
||||
|
||||
use core\task\manager;
|
||||
|
||||
|
||||
/**
|
||||
* A scheduled task to remove unneeded random questions.
|
||||
*
|
||||
* @copyright 2018 Bo Pierce <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class remove_unused_questions extends \core\task\scheduled_task {
|
||||
/**
|
||||
* Get a descriptive name for this task (shown to admins).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return get_string('taskunusedrandomscleanup', 'qtype_random');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute() {
|
||||
global $DB, $CFG;
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
|
||||
// Confirm, that there is no restore in progress to make sure we do not
|
||||
// clean up questions that have their quiz slots not restored yet.
|
||||
$restoretasks = [
|
||||
'\core\task\asynchronous_copy_task',
|
||||
'\core\task\asynchronous_restore_task',
|
||||
];
|
||||
|
||||
$running = manager::get_running_tasks();
|
||||
foreach ($running as $task) {
|
||||
if (in_array($task->classname, $restoretasks)) {
|
||||
mtrace('Detected running async restore. Aborting the task.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find potentially unused random questions (up to 5000).
|
||||
// Note, because we call question_delete_question below,
|
||||
// the question will not actually be deleted if something else
|
||||
// is using them, but nothing else in Moodle core uses qtype_random,
|
||||
// and not many third-party plugins do.
|
||||
$unusedrandomids = $DB->get_records_sql(
|
||||
" SELECT DISTINCT q.id, 1
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv on qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe on qbe.id = qv.questionbankentryid
|
||||
LEFT JOIN {question_references} qr on qr.questionbankentryid = qbe.id
|
||||
WHERE qr.questionbankentryid IS NULL
|
||||
AND q.qtype = ? AND qv.status <> ?",
|
||||
['random', 'hidden'],
|
||||
0,
|
||||
5000
|
||||
);
|
||||
|
||||
$count = 0;
|
||||
foreach ($unusedrandomids as $unusedrandomid => $notused) {
|
||||
question_delete_question($unusedrandomid);
|
||||
// In case the question was not actually deleted (because it was in use somehow),
|
||||
// it will be marked as hidden, so the query above will not return it again.
|
||||
$count += 1;
|
||||
}
|
||||
mtrace('Cleaned up ' . $count . ' unused random questions.');
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Definition of question/type/random scheduled tasks.
|
||||
*
|
||||
* @package qtype_random
|
||||
* @category task
|
||||
* @copyright 2018 Bo Pierce <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$tasks = [
|
||||
[
|
||||
'classname' => 'qtype_random\task\remove_unused_questions',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => '*',
|
||||
'day' => '*',
|
||||
'month' => '*',
|
||||
'dayofweek' => '*',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Tests of the scheduled task for cleaning up random questions.
|
||||
*
|
||||
* @package qtype_random
|
||||
* @copyright 2018 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
namespace qtype_random;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
||||
|
||||
|
||||
/**
|
||||
* Tests of the scheduled task for cleaning up random questions.
|
||||
*
|
||||
* @copyright 2018 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \qtype_random\task\remove_unused_questions
|
||||
*/
|
||||
final class cleanup_task_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test that remove_unused_questions deletes questions as appropriate.
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_cleanup_task_removes_unused_question(): void {
|
||||
global $DB, $USER;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// To do the test, we will be restoring a backup that contains 3 questions:
|
||||
// A non-hidden broken question, a hidden broken question, and a non-broken question.
|
||||
// Only the non-hidden broken question should be deleted,
|
||||
// because questions are hidden when a delete was attempted but failed,
|
||||
// and this is used to indicate we should skip over them in future deletes,
|
||||
// to avoid continuingly attempting to delete undeletable questions.
|
||||
|
||||
// Extract backup file.
|
||||
$backupid = 'test_cleanup_task_removes_unused_question';
|
||||
$backuppath = make_backup_temp_directory($backupid);
|
||||
get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
|
||||
__DIR__ . '/fixtures/broken_question_course.mbz',
|
||||
$backuppath
|
||||
);
|
||||
|
||||
// Do restore to new course with default settings.
|
||||
$categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
|
||||
$newcourseid = \restore_dbops::create_new_course('Broken Question Course', 'BQC', $categoryid);
|
||||
$rc = new \restore_controller(
|
||||
$backupid,
|
||||
$newcourseid,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
$USER->id,
|
||||
\backup::TARGET_NEW_COURSE
|
||||
);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
|
||||
// Check the hidden question was unhidden during the restore,
|
||||
// to make it eligible for deletion.
|
||||
$hiddenquestionid = $DB->get_field('question', 'id', ['name' => 'Random (BQC hidden broken question)']);
|
||||
$this->assertNotEquals(
|
||||
'hidden',
|
||||
$DB->get_field('question_versions', 'status', ['questionid' => $hiddenquestionid])
|
||||
);
|
||||
|
||||
// Revert the hidden question back to hidden, so we can check it isn't deleted.
|
||||
$DB->set_field('question_versions', 'status', 'hidden', ['questionid' => $hiddenquestionid]);
|
||||
|
||||
// Run the scheduled task.
|
||||
$task = new \qtype_random\task\remove_unused_questions();
|
||||
$this->expectOutputString("Cleaned up 1 unused random questions.\n");
|
||||
$task->execute();
|
||||
|
||||
// Verify.
|
||||
$this->assertFalse(
|
||||
$DB->record_exists('question', ['name' => 'Random (BQC non-hidden broken question)'])
|
||||
);
|
||||
$this->assertTrue(
|
||||
$DB->record_exists('question', ['name' => 'Random (BQC hidden broken question)'])
|
||||
);
|
||||
$this->assertTrue(
|
||||
$DB->record_exists('question', ['name' => 'BQC non-broken question'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that remove_unused_questions aborts when there is a course restore in progress.
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_cleanup_task_checks_for_active_restores(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Get ready the tasks.
|
||||
$cleanuptask = new \qtype_random\task\remove_unused_questions();
|
||||
$restoretask = new \core\task\asynchronous_restore_task();
|
||||
\core\task\manager::queue_adhoc_task($restoretask);
|
||||
$copytask = new \core\task\asynchronous_copy_task();
|
||||
\core\task\manager::queue_adhoc_task($copytask);
|
||||
|
||||
// Start the first adhoc task. This might be either restore or copy adhoc task.
|
||||
$task1 = \core\task\manager::get_next_adhoc_task(time());
|
||||
\core\task\manager::adhoc_task_starting($task1);
|
||||
$cleanuptask->execute();
|
||||
|
||||
// Complete the first task and start the second one.
|
||||
\core\task\manager::adhoc_task_complete($task1);
|
||||
$task2 = \core\task\manager::get_next_adhoc_task(time());
|
||||
\core\task\manager::adhoc_task_starting($task2);
|
||||
$cleanuptask->execute();
|
||||
|
||||
// Complete the second adhoc task.
|
||||
\core\task\manager::adhoc_task_complete($task2);
|
||||
$cleanuptask->execute();
|
||||
|
||||
$aborted = 'Detected running async restore. Aborting the task.';
|
||||
$completed = 'Cleaned up 0 unused random questions.';
|
||||
$this->expectOutputRegex("/.*$aborted.*\s.*$aborted.*\s.*$completed.*/");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -26,7 +26,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'qtype_random';
|
||||
$plugin->version = 2025041400;
|
||||
$plugin->version = 2025041401;
|
||||
|
||||
$plugin->requires = 2025040800;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user