Question behaviours
This page explains how to go about writing a new Question Interaction Model for the new Moodle question engine.
Previous section: Design
Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.
To learn to write Question Interaction Models, you are highly encouraged to read the code of some of the existing interaction models in the Moodle code base. The code for most interaction models is shorter than these instructions!
This document assumes that you understand the data model, where a question attempt comprises a number of steps, and each step has some properties like a state and a mark, and an array of submitted data.
File layout
A question interaction model plugin lives in a folder in question/interaction. The layout inside that folder follows the typical layout for a Moodle plugin. For example, inside question/interaction/mymodel/ we would have:
- model.php
- This contains the definition of the qim_mymodel class, which should extend the question_interaction_model base class.
- renderer.php
- This contains the definition of the qim_mymodel_renderer class, which should extend the qim_renderer base class.
- simpletest/...
- Contains the unit tests for this interaction model. You are strongly encouraged to write thorough unit tests to ensure the correctness of your interaction model.
- lang/en_utf8/qim_mymodel.php
- English language strings for this interaction model. You can, of course, include other languages too. The language file must define at least the string giving the model a name, for example $string['mymodel'] = 'My model';.
Note that Question Interaction Models are not expected to have their own database tables or capabilities. Therefore, there should not be a db sub-folder.
In future, we will probably add the ability for an Interaction Model to have a settings.php file, so that it can have configuration options.
Question model class
One instance of this class will be created for each question_attempt that uses this model.
This documentation just lists the metods that you are likely to need to override. See the PHPdocumentor comments for a complete API documentation.
Class declaration
The class name for the model class must be the plugin name (e.g. mymodel) prefixed by qim_. You must extend the question_interaction_model base class, or another model class.
class qim_mymodel extends question_interaction_model {
// ...
}
You should not need to override the constructor.
Fields
When an interaction model class is created, the fields qa and question are initialised to point to the question attempt and question that this model is being used by.
From the base class code:
// From the base class.
/** @var question_attempt */
protected $qa;
/** @var question_definition */
protected $question;
public function __construct(question_attempt $qa) {
$this->qa = $qa;
$this->question = $qa->get_question();
}
IS_ARCHETYPAL
Some interaction models define models that should be offered as options in the user interface. For example CBM, interactive mode, and so on. Other models are for internal use. For example qim_informationitem, qim_interactiveadaptedformyqtype. This is indicated by defining a constant IS_ARCHETYPAL in the class. True means that this model will be offered as an option in the UI.
const IS_ARCHETYPAL = true; // or false
get_min_fraction
Returns the smallest possible fraction that this model, applied to this question, may give. Normally this will be the min fraction or the question type, however some models (for example CBM) manipulate the question scores, and so need to return an adjusted min_fraction.
For example, from qim_deferredfeedback:
public function get_min_fraction() {
return $this->question->get_min_fraction();
}
From qim_qim_deferredcbm:
public function get_min_fraction() {
return question_cbm::adjust_fraction(
parent::get_min_fraction(), question_cbm::HIGH);
}
get_expected_data
Some interaction models display additional form controls. When the question from is submitted, these submitted values are read using the standard optional_param function. The question engine needs to know what parameters to ask for, and with what types.
The get_expected_data function should return an array of expected parameters, with the corresponding param type. Note that the array of expected parameters may depend on the current state of the question attempt. For example, from qim_deferredcbm
public function get_expected_data() {
if (question_state::is_active($this->qa->get_state())) {
return array('certainty' => PARAM_INT);
}
return array();
}
init_first_step
You are unlikely to need to override this method. This is called when the question attempt is actually stared. It gives the model a chance to do any necessary initialisation, including passing on the request to the question type. This is, for example, how the choices in a multiple choices question are shuffled.
From the base class:
public function init_first_step(question_attempt_step $step) {
$this->question->init_first_step($step);
}
process_action
This is the most important method. It controls what happens when someone does something with the question (other than starting it, which is handled by init_first_step.)
When the method is called, a pending attempt step is passed in. This method can either return false, to indicate nothing interesting happened, and the pending step should be discarded. Or, it can update that step, and return true, to have the new step retained.
Typically, the method is implemented by examining the current state of the attempt, and the pending step, to decide what sort of action this is, and then delegating to a more specific method.
For example, from qim_deferredfeedback
public function process_action(question_attempt_step $pendingstep) {
if ($pendingstep->has_im_var('comment')) {
return $this->process_comment($pendingstep);
} else if ($pendingstep->has_im_var('finish')) {
return $this->process_finish($pendingstep);
} else {
return $this->process_save($pendingstep);
}
}
Note that there are some special actions that every model must be able to handle:
- im_var comment => 'A manual comment'
- This may also have im_var mark and im_var maxmark, if the question is graded. This is the action that is fired when a user manually grades a question.
- im_var finish => 1
- This is the action that is generated when the user submits and finishes a whole usage. For example when they click 'Submit all and finish' in a quiz.
The base class implements the process_comment method in a way that should be suitable for most models. It also provides a process_save implementation that may be suitable for most models.
adjust_display_options
This method is called before a question is rendered, so that the interaction model can change the display options depending on the current state of the question. So, for example, after the question is finished, it should be displayed in read-only mode. Before the student had submitted an answer, no feedback should be displayed.
For example, the base class does
public function adjust_display_options(question_display_options $options) {
if (question_state::is_finished($this->qa->get_state())) {
$options->readonly = true;
} else {
$options->hide_all_feedback();
}
}
Renderer class
Renderers are all about generating HTML output. The overall output of questions is controlled by the core_question_renderer::question(...), which in turn calls other methods of itself, the question type renderer and the interaction model renderer, to generate the various bits of output. See this overview of the parts of a question.
Once again, in what follows, I only list the methods your are most likely to want to override.
controls
This is a block of output in the question formulation area. It goes after all the question type output in this area.
Two examples. This method is used by the interactive model to show the Submit button (if the question is in an appropriate state). It is used by the CBM model to show the certainty choices.
feedback
This is a block of output in the feedback area of the question. It is used, for example in interactive mode, to show the Try again button.
Unit tests
For a general introduction, see the Moodle unit testing documentation.
Most of the interaction models are currently tested by working one or more test questions through a sequence of example inputs. To do this they subclass qim_walkthrough_test_base which provides a lot of helper methods to facilitate writing tests like that.
The best way to start is probably to look at the tests for some of the standard interaction models.
See also
In the next section, Developing a Question Type I describe what a developer will need to do to create a Question Type plugin for the new system.
- Back to Question Engine 2