Skip to content

如何创建一个Assignment Type插件

7lemon edited this page May 25, 2012 · 10 revisions

如何创建一个Assignment Type插件

本向导将会指引你如何在Moodle中创建一个Assignment Type插件。以下所有示例基于Moodle2.1版本,不保证能够完全兼容旧版本Moodle。

如果你不熟悉Moodle的代码规范,建议在开始前先阅读 Coding Guidelines

1. 说明

本文代码示例中的 PLUGINNAME 在实际编程中都要替换成小写的插件名称。

所有代码都要放在 <moodleroot>/mod/assignment/type/PLUGINNAME 文件夹下。

也可以参考其他内置的Assignment Type插件,学习他们的代码从而掌握Assignment Type类型插件的写法。内置插件放在了 <moodleroot>/mod/assignment/type 文件夹内。

2. 新建Assignment Type插件

2.1 主文件

首先在插件文件夹内需要创建一个php文件,命名为assignment.class.php。定义一个作业类型的类,先像下面那样写好初始化方法。

<?php

require_once($CFG->libdir.'/formslib.php');

class assignment_PLUGINNAME extends assignment_base {

    function assignment_PLUGINNAME($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
        parent::assignment_base($cmid, $assignment, $cm, $course);
        $this->type = 'PLUGINNAME';
    }
}

每个作业的实例被创建时都会执行assignment_PLUGINNAME方法。稍后根据实际需要可以给类加入一些属性,然后编辑此方法,设置一些初始化参数。在加入自定义的属性时需要注意,先查看 <moodleroot>/mod/assignment/lib.php 中的 assignment_base 类,不要和其中的属性冲突。

至此,这个Assignment Type已经可以工作了。可以正常安装到Moodle中,打开课程编辑模式,在 添加活动 下拉列表中能够看到 PLUGINNAME 。 作业类型的名字显示成 PLUGINNAME 是因为还没定义语言包翻译此字符串,Moodle默认显示成了此格式。

2.2 语言包

在插件文件夹中创建一个叫做lang的文件夹,所有语言包都要放在这个文件夹内。Moodle会根据站点的语言设置自动找到其中对应的语言包,翻译定义的字符串。

2.2.1 英文语言包

lang 文件夹内新建一个名字叫做 en 的文件夹,代表英文语言包。Moodle如果查找不到和站点语言对应的语言包时,就会将英文语言包作为缺省语言包。在 en 文件夹内创建一个名为 assignment_PLUGINNAME.php 的文件,所有和此插件有关的需要翻译的字符串都放在这个文件中。

下面给出一段示例,先将 PLUGINNAME 翻译过来,再随便写点其他的。

<?php

$string['PLUGINNAME'] = 'My First Plugin';
$string['textidentifier'] = 'Some text I want to display.';

保存后再次打开 添加活动 下拉列表时,就会发现原来的 PLUGINNAME 变成了 My First Plugin。如果没有效果,尝试清空Moodle的缓存。

2.2.2 简体中文语言包

lang 文件夹内新建 zh_cn 文件夹,像创建英文语言包一样新建一个 assignment_PLUGINNAME.php 文件。或者直接把英文语言包的文件复制过来再做修改。

<?php

$string['PLUGINNAME'] = '我的第一个插件';
$string['textidentifier'] = '我想要显示的一些文字。';

同样保存后如果站点设置的语言是简体中文,应该能得到正确的显示结果。如果仍然显示英文或无法翻译,请检查代码是否正确,并清空Moodle的缓存后重试。

2.2.3 语言包的使用

使用Moodle的 get_string 函数获取字符串翻译。例如想要输出上述 textidentifier 字符串的翻译,应使用如下代码:

echo get_string('textidentifier', 'assignment_PLUGINNAME');

更多有关get_sting函数的使用说明,请参考官方文档或查看Moodle源代码(函数定义位于 <moodleroot>/lib/moodlelib.php)。

2.3 数据库

一般来说,如果作业要求学生提交的信息不多并且不需要做一些复杂的处理的话,可以直接将学生提交的内容存入 assignment_submissions表的data1data2字段中。如果需要创建数据库,像创建其他插件的数据库一样,需要新建一个 version.php 文件以及 db/install.xml 文件。如果今后数据库有变动,还需要创建 db/upgrade.php 来升级数据库。

2.3.1 version.php

<?php
$plugin->version = 20120322;    // 格式YYYYMMDD,即年月日,代表此插件何时被创建
$plugin->requires = 2010102600; // 查看<moodleroot>/mod/assignment/version.php中的版本号,这里代表插件需要的最小版本号

如上此文件中定义了插件的版本,当插件版本发生变动时,Moodle会将此文件中的版本号和其数据库中记录的版本号比较,如果较新将提示升级。注意$plugin->version 的值应和 install.xml 中XMLDB元素的 VERSION 属性的值一致。

2.3.2 install.xml

这个文件是数据库表结构的XML定义。请使用Moodle自带的XMLDB编辑器创建和编辑此文件。在 Moodle->网站管理->开发->XMLDB编辑器 中找到你的插件 mod/assignment/type/PLUGINNAME/db。点击“创建”按钮,Moodle将自动在插件目录下创建db文件夹和 install.xml 文件。创建成功后,点击“载入”按钮,载入完成时“编辑”按钮会亮起,点击“编辑”开始编辑数据库表结构。

Moodle会默认在其中创建一个名字叫 PLUGINNAME 的表,你也可以随意更改表名。在编辑过程中,数据库结构不会发生任何改变,改变的只有 install.xml 文件。即使保存后你也会发现数据库没有改变,刚刚建立的表也没有添加到数据库中。Moodle只在新安装一个插件的时候会检查 install.xml 文件并按照其中的结构创建数据表。在开发过程中如果发生数据库表结构改变,而你又不想编辑 upgrade.php 文件,可以使用XMLDB编辑器中的“从MySQL建新表”或“查看SQL代码”再到MySQL创建表。

2.3.3 upgrade.php

一旦插件发布到正式环境,再需要更改 install.xml 文件的话就要编辑 version.php 将版本号提高。使用XMLDB编辑器更改 install.xml 文件,然后编辑 upgrade.php 将最新的修订用php代码写明。

这里的php代码有一部分是可以使用XMLDB编辑器生成的。在XMLDB编辑器中点击“查看PHP代码”链接即可见。

<?php

function xmldb_assignment_PLUGINNAME_upgrade($oldversion = 0) {
    global $DB;
    $dbman = $DB->get_manager();

    $result = true;

    if ($result && $oldversion < XXXXXXXX) {
        // XMLDB提供的代码(可能需要一些处理)
    }
    
    return $result;
}

当写好 upgrade.php 后,Moodle管理员再次登入站点或查看通知页面时,Moodle就会告知此插件版本发生改变。继续时,Moodle会调用此文件中的 xmldb_assignment_PLUGINNAME_upgrade 函数,执行其中的数据库升级过程。

2.4 表单

3. 功能扩展

按照上一章的方法创建的作业插件甚至连提交作业的功能都没有,本章将会介绍一些 assignment_base 类中的方法。你可以在插件的 assignment_PLUGINNAME 类中重载这些方法,按照你的需求为它们定义不同的行为。

3.1 作业界面

3.1.1 View方法

  1. view(): 当用户进入一个作业的页面时,view.php 会被执行。从代码中可以看出,它先根据作业的类型创建一个对应的作业实例对象,然后调用其中的 view 方法:$assignmentinstance->view();。参照 assignment_base::view() 方法做适当修改就能得到你需要的效果。其实可以将其看做是程序的唯一入口,后面的工作只要按照你喜欢的开发模式去做就可以了。
  2. view_header(): 这个方法输出页面页眉,设置页面标题,打印分组菜单。如果你想在作业页面显示出来的时候在其顶部增加一些信息,可以在 aasignment_PLUGINNAME 类中添加此方法。使用 parent::view_header() 预先调用默认的 view_header() 方法,将其中的信息输出到页面,之后再添加你的自定义代码。当然如果你想改变 view_header() 的行为,也可以直接自己写一个。
  3. view_intro(): 创建一个作业时,必须填写一个作业描述,这个方法用来显示作业描述。
  4. view_dates(): 这个方法默认显示作业的开放时间和截止时间。如果需要添加其他时间,将 lib.php 中的此方法复制到 assignment_PLUGINNAME 类中,参考其中已有的两项输出时间的代码,在table标签中添加时间。
  5. view_footer(): 这个方法输出页面的页脚,一般不需要修改。
  6. view_feedback(): 该方法默认打印教师给学生的反馈。将教师的头像,名字,评分时间以及评语打印到作业页面上。它接受一个submission对象参数(即学生提交的作业),如果参数为空会取当期登录用户提交的作业。在 view 方法中调用此方法时可以根据实际需要决定是否需要显示指定学生的反馈信息。
  7. submittedlink(): 这个方法返回一个描述当前作业状态的链接。它被 view_header 调用,在页面右上角输出链接。对于教师,它输出当前已有多少学生提交了作业;对于学生,它输出学生提交作业的时间。在它的代码中我们可以看到使用了has_capability('mod/assignment:grade', $context)来判断当前用户是教师还是学生。在以后的插件编写中,同样可以使用此方法来判断一个用户身份。但是如果你的代码中有多次需要判断用户身份,建议在初始化时将它存到一个属性中方便后面调用。

3.1.2 示例

也可以查看几个默认的作业类型,学习它们的 view() 方法写法。

<?php

class assignment_PLUGINNAME extends assignment_base {

    ...

    function view() {

        $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
        require_capability('mod/assignment:view', $context);

        add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
               $this->assignment->id, $this->cm->id);

        $this->view_header();

        $this->view_intro();
    
        // 在这里编写你的程序逻辑
        echo 'Hello, my assignment.';

        $this->view_dates();

        $this->view_feedback();

        $this->view_footer();
    }
}

3.2 提交作业

3.2.1 创建表单

作业插件可能需要学生填写表单来提交作业,那么不应该直接使用HTML构建表单,正确的方法是使用Moodle的表单类来创建表单。这样Moodle会帮你做好一些数据过滤和验证的控制。在 assignment_PLUGINNAME.php 中(或单独创建一个文件存储插件所有表单定义)定义一个表单类,继承自Moodle的 moodleform 类。编写表单定义方法,加入需要的表单元素。

<?php

class mod_assignment_PLUGINNAME_XXX_form extends moodleform {
    function definition() {
        $mform = $this->_form;
        // $this->_customdata 传递给表单类的自定义参数
        
        $mform->addElement('text', 'url', get_string('url', 'assignment_PLUGINNAME'));
        $mform->setType('url', PARAM_TEXT);
        $mform->addRule('url', get_string('required'), 'required', null, 'client');
        ...
        $this->add_action_buttons(); // 默认添加一个保存按钮和一个取消按钮
    }
}

表单定义完成后,便可以在作业界面将其显示出来。具体的方法如下:

<?php

// 创建表单对象,指明表单提交的链接$url,需要传递给表单类的数据$customdata
$mform = new mod_assignment_PLUGINNAME_XXX_form($url, $customdata);

// 打印表单
$mform->display();

当用户提交表单时,Moodle首先会根据表单定义中的内容将表单各个字段中的数据过滤,我们在程序中将会得到按照其规则过滤后的字符串。例如指定某字段填入的必须是URL,那么不符合一个URL规则的字符将被过滤掉。所以获取用户提交的数据时也应当使用表单类中的方法。

<?php
$mform = new mod_assignment_PLUGINNAME_XXX_form(); // 参数可以省略(注意可能引起PHP警告)
if (!$mform->is_cancelled()) { // 如果表单定义含有取消按钮,点击取消按钮也算是提交表单,使用此方法判断
    $data = $mform->get_submitted_data();
    echo $data->url; // $data是以表单字段为属性的对象
}

有关Moodle表单的详细文档,见 Form Definition

3.2.2 处理提交

通常,assignment_submissions 表用来存储和学生作业有关的内容,包括学生提交的信息,提交的时间以及教师的反馈。建议把所有提交都在这个表中生成一个记录,以便使用 assignment 内置的方法统计提交信息、评分和反馈等。但是在 assignment_base 类中并未直接定义一个更新学生提交的作业的方法。在这里给出一个模板供参考:

<?php

class assignment_PLUGINNAME extends assignment_base {

	function update_submission($userid, $data) {
        global $DB;
        
        // 取出指定用户的提交。第二个参数默认为false,这里使用true表示如果未找到则创建一条新的记录
        $submission = $this->get_submission($userid, true);

        $update = new stdClass();
        $update->id = $submission->id;
        
        // assignment_submissions表中有两个字段data1和data2用来存储作业有关的信息
        $update->data1 = $data->something1;
        $update->data2 = $data->something2;
        $update->timemodified = time();

        $DB->update_record('assignment_submissions', $update);

        $submission = $this->get_submission($userid);
        $this->update_grade($submission);
        return $submission;
    }
}

有了这个方法,就可以在程序中直接使用update_submission($userid, $data);来创建或更新学生的提交。使用get_submission()方法直接获得某个学生的提交。使用get_submissions()方法能够获得一次作业中所有已交的作业。具体请查看 assignment_base 类中的定义。

3.3 评分界面

教师为学生的作业评分之前,经常需要进入 查看已交作业 的页面查看那些学生已经提交作业,进入 反馈 页面给学生的作业打分。修改下面两个方法

3.3.1 print_user_answer

查看已交作业 的页面中有参与课程的学生(或某个分组)列表,这个表中有一列名为 最后修改(提交的作业)。在生成这个表格时,Assignment会调用指定类型作业中的 print_user_answer 方法,将此类作业需要显示给教师的学生提交信息在这里展示给教师。如果在插件中没有定义此方法,这里只会显示一个最后修改的时间(学生最后一次提交作业的时间)。

<?php

function print_student_answer($userid, $return=false) {
    global $OUTPUT;

    if (!$submission = $this->get_submission($userid)) {
        return '';
    }
    
    $output = '<span class="red">Student</span>\'s answer';

    return $output;
}

在输出的内容中可以使用任意HTML标签来得到特定的显示效果。如果想在此处显示学生上传的文件,参考 uploadsingle 类型作业中的此方法。

3.3.2 print_user_files

反馈 页面的表单中,最顶部的一项 提交的作业 使用作业类的 print_user_files 方法显示。在列表中由于空间有限,一般不希望显示完整的作业内容而只显示一段摘要。那么在此处,可以完整地展示学生作业的内容供教师评分时参考。当然,也可以直接使用 print_student_answer 方法返回的结果。

3.4 自定义设置项

新建一个作业活动时,在作业的设置界面为每个插件都提供了一个可自定义显示的设置框,默认是空白的。如果觉得系统自带的设置不够,可以在程序中添加一些自定义设置参数。

3.4.1 setup_elements

在 assignment_PLUGINNAME 类中添加一个 setup_elements(&$mform) 方法。Assignment在assignment表中提供了5个扩展字段var1,var2,var3,var4,var5用来存储作业的自定义设置参数。在这个方法中,直接调用 $mform 的添加表单元素的方法来扩展设置页面的表单。下面给出一个模板,也可以参照 assignment_upload 中的示例。

<?php

function setup_elements(&$mform) {
    global $CFG, $COURSE;

    // 在此增加表单项目
    $ynoptions = array( 0 => get_string('no'), 1 => string('yes'));
    $mform->addElement('select', 'var1', string('allowresubmit', 'assignment'), $ynoptions);
    $mform->addHelpButton('var1', 'allowresubmit', 'ignment');
    $mform->setDefault('var1', 0);

    // 这两行是必须的,不要删掉
    $course_context = get_context_instance(CONTEXT_COURSE, $COURSE->id);
    plagiarism_get_form_elements_module($mform, $course_context);
}

3.4.2 add_instance & update_instance

可能你的作业插件设置比较复杂,需要设置的自定义参数非常多,5个字段不够用;或者你想在作业创建时,做一些其他的操作。在作业的设置提交后,Moodle会创建一个名为 $assignment 的对象,这个对象含有设置页面表单中各个项目的所有字段。默认的 add_instance() 方法直接将其存入数据库中。

在存入数据库之前,你可以对这些数据做一些处理。在你的 assignment_PLUGINNAME 类中添加 add_instance() 方法操作 $assignment 对象中的数据。如果像下面代码这样对 assignment 表中的字段进行了一些修改,那么一般需要保证 update_instance() 方法中也做了同样的处理。

<?php

function add_instance($assignment) {

    // 处理 $assignment,例如:
    $assignment->var1 = 1;
    $assignment->var2 = 'Try this';
    $assignment->var3 = json_encode(array(1, 2, 3));
    
    return parent::add_instance($assignment);
}

3.4.3 form_data_preprocessing

像3.4.1节中的代码示例那样,直接使用var1做表单项的名字,保存时会将数据直接存到 assignment 表的var1字段。但是如果不想使用var1这样的名字,或者希望给若干数据打包后存入var1字段,除了需要像3.4.2节那样处理数据外,还需要让程序再次进入编辑作业的页面时,能够得到正确的显示结果。这时,需要使用 form_data_preprocessing() 方法对填入表单的数据做一些处理。

将3.4.1节代码示例中的 var1 改为 test,在编辑作业页面会得到和刚才一样的显示效果。但是提交后不会对这个作业的记录产生任何影响,因为 assignment 表中并没有名为 test 的字段。现在我们把它存到 var1 字段,于是:

<?php

function add_instance($assignment) {

    $assignment->var1 = $assignment->test;
    return parent::add_instance($assignment);
}

function update_instance($assignment) {

    $assignment->var1 = $assignment->test;
    return parent::update_instance($assignment);
}

再试一次编辑作业,更改“是否允许重交”的选项保存后再回到编辑页面。发现不管该成何值编辑页面上的“是否允许重交”还是默认的“否”。这是因为 assignment 表中并没有 test 字段,而程序也不知道现在 test 项目的值对应到 var1 上,所以一直是“否”。添加下面的代码,将 var1 字段的值传递给 test 选项。

<?php

function form_data_preprocessing(&$default_values, $form) {

    $default_values['test'] = $default_values['var1'];
}