
Moodle 自定义题型在课程复制后报“Can't find data record”错误,根本原因在于未实现题型专属的备份与还原逻辑;本文详解如何通过编写 backup.class.php 和 restore.class.php 文件,使自定义题型数据随课程完整复制。
moodle 自定义题型在课程复制后报“can't find data record”错误,根本原因在于未实现题型专属的备份与还原逻辑;本文详解如何通过编写 `backup.class.php` 和 `restore.class.php` 文件,使自定义题型数据随课程完整复制。
在开发 Moodle 自定义题型(如 qtype_aligator)时,一个常见却易被忽视的关键点是:课程复制(Course Copy)并非仅复制题干和基础元数据,还需同步复制题型专属的扩展表数据(如 qtype_aligator_options)。若未显式声明并处理这些关联数据的备份与还原流程,Moodle 在复制后加载题目时会因找不到对应 questionid 的记录而抛出 invalidrecord 异常(如 SELECT * FROM {qtype_aligator_options} WHERE questionid = 169 失败)。
该问题与 save_question_options() 或 get_question_options() 的实现无直接关系——你的代码逻辑本身正确(插入/更新选项表),但 Moodle 的课程复制机制完全绕过常规保存流程,而是依赖插件提供的备份/还原类来序列化和重建题型数据。因此,即使数据库操作无误,缺失 backup/ 和 restore/ 目录下的适配文件,将导致题型扩展数据“静默丢失”。
✅ 正确解决方案:为题型补全 Moodle 2.0+ 备份还原框架
需在题型插件目录下创建以下结构:
/question/type/aligator/
├── backup/
│ └── backup-qtype-aligator-plugin.class.php
└── restore/
└── restore-qtype-aligator-plugin.class.php
1. backup/backup-qtype-aligator-plugin.class.php 示例(精简核心):
<?php defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/type/aligator/backup/moodle2/backup_qtype_aligator_plugin.class.php');
class backup_qtype_aligator_plugin extends backup_qtype_plugin {
protected function define_question_plugin_structure($wrapper) {
$plugin = $wrapper->add_plugin_element('qtype_aligator', null, 'questionid');
$plugin->add_child(new backup_nested_element('options', null, array(
'id', 'questionid', 'custom_input', 'wkz'
)));
// 关联 questionid → 新 questionid 映射(关键!)
$plugin->set_source_table('qtype_aligator_options', array('questionid' => backup::VAR_PARENTID));
return $plugin;
}
}
2. restore/restore-qtype-aligator-plugin.class.php 示例:
<?php defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/type/aligator/restore/moodle2/restore_qtype_aligator_plugin.class.php');
class restore_qtype_aligator_plugin extends restore_qtype_plugin {
protected function process_options($data) {
global $DB;
$data = (object)$data;
$oldquestionid = $data->questionid;
// 获取新生成的 questionid(由 restore 过程自动映射)
$newquestionid = $this->get_new_parentid('question_created', $oldquestionid);
if (!$newquestionid) {
throw new restore_step_exception('error_question_not_restored', 'qtype_aligator');
}
$data->questionid = $newquestionid;
$data->id = null; // 确保插入新记录,而非更新
$DB->insert_record('qtype_aligator_options', $data);
}
protected function define_question_plugin_structure($parser, $step) {
$plugin = $step->get_schema_parser();
$plugin->process('options', array($this, 'process_options'));
return $plugin;
}
}
⚠️ 关键注意事项:
- 命名规范严格:文件名、类名必须与题型标识符(aligator)一致,且遵循 backup_qtype_{name}_plugin / restore_qtype_{name}_plugin 格式;
- 映射逻辑不可省略:$this->get_new_parentid('question_created', $oldquestionid) 是核心,它将原题目的 questionid 正确映射到新课程中生成的新 ID;
- 路径与命名空间:确保文件位于 question/type/{qtype}/backup/ 和 question/type/{qtype}/restore/ 下,且 backup/ 和 restore/ 目录需存在于插件根目录;
- 版本兼容性:Moodle 3.9+ 要求使用 Moodle 2 备份格式(即 moodle2/ 子目录),旧版 backuplib.php 已废弃;
- 测试验证:部署后务必执行一次完整课程复制,并检查目标课程中题目的选项表(qtype_aligator_options)是否生成对应新 questionid 记录。
总结:Moodle 题型的可复制性不取决于 CRUD 操作,而取决于是否向系统注册了正确的备份/还原契约。补全 backup 和 restore 类,本质是告诉 Moodle “我的题型额外存了哪些数据,以及如何安全地迁移到新上下文”。这是官方插件(如 qtype_essay)的标准实践,也是你解决 invalidrecord 错误的唯一可靠路径。











