░▒▓████████████████████████████████▓▒░ ░▒▓█ ▓▒░ ░▒▓█ ~ S W A M P ~ ▓▒░ ░▒▓█ ▓▒░ ░▒▓████████████████████████████████▓▒░
pplmvsvm
LOCATION:
/var/www/moodle/public/question/type/multianswer/classes/task
☗ ROOT
↻ REFRESH
✎ CARVE FLESH
EDITING: cleanup_duplicate_subquestions.php
<?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/>. namespace qtype_multianswer\task; use context_system; use core\task\stored_progress_task_trait; use core_question\local\bank\question_version_status; use question_bank; use question_engine_data_mapper; /** * Cleanup duplicate subquestions * * Due to MDL-85721, there may be duplicated subquestions in the database. These have a question bank entry, question version, * and question record with a parent, but they are not referred to in that parent's sequence. * * @package qtype_multianswer * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net} * @author Mark Johnson <mark.johnson@catalyst-eu.net> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cleanup_duplicate_subquestions extends \core\task\adhoc_task { use stored_progress_task_trait; /** * Find questions where there are other questions with identical text, stamp and multianswer parent * * We may have multiple subquestions with the same stamp but different text or parents due to historical bugs, * so this includes the ID field from one of the duplicates to ensure we have a unique first field. * * @return array */ public function find_duplicated_subquestions(): array { global $DB; $questiontext = $DB->sql_cast_to_char('subq.questiontext'); $sequence = $DB->sql_cast_to_char('qm.sequence'); return $DB->get_records_sql(" SELECT MIN(subq.id) AS firstid, subq.stamp AS stamp, {$questiontext} AS questiontext, subq.parent, {$sequence} AS sequence, COUNT(1) AS count FROM {question} subq JOIN {question} q ON q.id = subq.parent JOIN {question_multianswer} qm ON q.id = qm.question WHERE q.qtype = 'multianswer' GROUP BY subq.stamp, {$questiontext}, subq.parent, {$sequence} HAVING COUNT(1) > 1 "); } #[\Override] public function execute() { global $CFG, $DB; require_once($CFG->libdir . '/questionlib.php'); $duplicatedsubquestions = $this->find_duplicated_subquestions(); $duplicatedcount = count($duplicatedsubquestions); if ($duplicatedcount === 0) { mtrace("No duplicated questions found."); return; } mtrace("Found {$duplicatedcount} subquestions with duplicates."); $this->start_stored_progress(); $progress = $this->get_progress(); foreach ($duplicatedsubquestions as $subquestion) { // Find instances of the subquestion that do not appear in the sequence of the parent. [$insql, $inparams] = $DB->get_in_or_equal(explode(',', $subquestion->sequence), equal: false); $params = array_merge([$subquestion->parent, $subquestion->stamp], $inparams); $duplicates = $DB->get_records_select('question', "parent = ? AND stamp = ? AND id {$insql}", $params); $duplicatecount = count($duplicates); // Delete each duplicate, with a progress bar. mtrace(""); mtrace("Deleting {$duplicatecount} duplicates:"); $progress->start_progress($subquestion->stamp, $duplicatecount); foreach ($duplicates as $duplicate) { // Based on question_delete_question(), without checking for parent usage or deleting children. // If the question is being used, just mark it as hidden. Otherwise, delete the question, version and question bank // entry. $sql = "SELECT qv.id as versionid, qv.version, qbe.id as entryid, qc.id as categoryid, ctx.id as contextid FROM {question} q LEFT JOIN {question_versions} qv ON qv.questionid = q.id LEFT JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid LEFT JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid LEFT JOIN {context} ctx ON ctx.id = qc.contextid WHERE q.id = ?"; $questiondata = $DB->get_record_sql($sql, [$duplicate->id]); // Do not delete a question if it is used by an activity module. Just mark the version hidden. if (questions_in_use([$duplicate->id])) { $DB->set_field( 'question_versions', 'status', question_version_status::QUESTION_STATUS_HIDDEN, ['questionid' => $duplicate->id] ); $progress->increment_progress(); continue; } // This sometimes happens in old sites with bad data. if (!$questiondata->contextid) { debugging('Deleting question ' . $duplicate->id . ' which is no longer linked to a context. ' . 'Assuming system context to avoid errors, but this may mean that some data like files, ' . 'tags, are not cleaned up.'); $questiondata->contextid = context_system::instance()->id; $questiondata->categoryid = 0; } // Delete previews of the question. $dm = new question_engine_data_mapper(); $dm->delete_previews($duplicate->id); // Delete questiontype-specific data. question_bank::get_qtype($duplicate->qtype, false)->delete_question($duplicate->id, $questiondata->contextid); // Finally delete the question record itself. $DB->delete_records('question', ['id' => $duplicate->id]); $DB->delete_records('question_versions', ['id' => $questiondata->versionid]); $DB->delete_records('question_references', [ 'version' => $questiondata->version, 'questionbankentryid' => $questiondata->entryid, ]); delete_question_bank_entry($questiondata->entryid); question_bank::notify_question_edited($duplicate->id); // Log the deletion of this question. $duplicate->category = $questiondata->categoryid; $duplicate->contextid = $questiondata->contextid; $event = \core\event\question_deleted::create_from_question_instance($duplicate); $event->add_record_snapshot('question', $duplicate); $event->trigger(); $progress->increment_progress(); } $progress->end_progress(); } } }
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 cleanup_duplicate_subquestions.php
PHP
7.4 KB
2026-02-11 01:44
EDIT