Development Guide

Test Question Plugins

This plugin slot has already been published with ILIAS 3.10 but without a description. We try to document it for test questions based on ILIAS 5.3 and add notes for further changes.

Please note that with a planned refacturing of the test & assessment in a future ILIAS version (e.g. introduction of a question service), this plugin slot will undergo heavy changes.

Contact:

Plugin Slot Description

 
The goal of test question plugins is to provide new question types in tests of ILIAS. A new type should behave like a built-in question type regarding creation, deletion, copy, editing, preview and print view, usage in tests, evaluation and export/import of questions and test results.
 
Please make sure that you have read the general plugin implementation documentation.
 
This slot is defined by the TestQuestionPool Module of ILIAS and named "Questions". This means all plugins have to be installed into directories at:
Customizing/global/plugins/Modules/TestQuestionPool/Questions/<Plugin_Name>
 
The ID of the TestQuestionPool Module is "qpl", the ID of the slot is "qst". These are used as prefixes together with your plugin id for database tables and for language variable identifiers:
 
DB Table Prefix (convention): il_qpl_qst_<Plugin_ID>_
Language Variable Prefix (automatically set): qpl_qst_<Plugin_ID>_

Plugin Directory Structure

A test question plugin has the following minimum file/directory structure. If needed, additional files in the templates/sql/lang directories may be used (see general plugin documentation). Another language file than ilias_en.lang may be present instead.

<PluginName> (Directory)
classes (Directory)
export (Directory)
qti12 (Directory)
class.<PluginName>Export.php
import (Directory)
qti12 (Directory)
class.<PluginName>Import.php
class.<PluginName>.php
class.<PluginName>GUI.php
class.il<PluginName>Feedback.php
class.il<PluginName>Plugin.php
lang (Directory)
ilias_en.lang
sql (Directory)
dbupdate.php
templates (Directory)
tpl.il_as_qpl_<PluginId>_output.html
tpl.il_as_qpl_<PluginId>_output_solution.html
plugin.php

classes/class.<PluginName>.php

Please note the missing il prefix in the class name. This class must be derived from assQuestion. It must implement the functions given in the example question type below.

classes/class.<PluginName>GUI.php

Please note the missing il prefix in the class name. This class must be derived from assQuestionGUI. It must implement the functions given in the example question type below.

classes/class.il<PluginName>Feedback.php

Please note that here the first letter of the plugin name is capitalized when being added to the il prefix. This class provides functions for answer specific feedback. If you don't want to provide answer specific feedback you can just extend it from
Modules/TestQuestionPool/classes/feedback/class.ilAssSingleOptionQuestionFeedback.php
without any further method definition.

classes/class.il<PluginName>Plugin.php

This is the basic plugin file. The plugin name and question type name defined in this class are identical and typically begin with "ass", e.g. "assExampleQuestion". Please note that the class name is il followed by the plugin name without changing any case, e.g. "ilassExampleQuestion".

classes/export/qti12/class.<PluginName>Export.php

This class generates the XML structure of the question for an export of a test or question pool. It has to be included and called by <PluginName>::toXML(). The parent method can't be used because the include path differs for plugins.

classes/import/qti12/class.<PluginName>Import.php

This class creates a question from the XML structure of an imported of a test or question pool. It is included and called by <PluginName>::fromXML(). The parent method can't be used because the include path differs for plugins.

lang/ilias_en.lang

At least the English language file hast to be present with at least one entry for the question type name. The language variable is the plugin name, e.g.:

// English Language File for assExampleQuestion Plugin
<!-- language file start -->
assExampleQuestion#:#Example Question

sql/dbupdate.sql

At least one database update step is needed to create the question type entry in the table qpl_qst_type. See the example question type.

templates/

The template files used by <PluginName>GUI. Normally you need at least two templates: one for getTestOutput() which presens the form for a participant and one for getSolutionOutput() which shows the solutions entered by a participant or the best solution for a question. The template file names can be custom.

plugin.php

The plugin description file. See the general plugin implementation documentation.

Example Question Plugin

The requirements for a question type plugin are at best explained with an example. You may clone an example question type from GitHub (works with ILIAS 5.3):
 
https://github.com/ilifau/assExampleQuestion.git
 
This question type is just used to explain and test the plugin slot. It works but makes no sense. The functionality is as follows:

  • Authors just enter the common data for all question types and additionally the maximum points given to a question (normally these points are calculated from other question properties).
  • Test participants get two input fields:
    • Value1 is stored as a result but not used for points calculation.
    • Value2 allows a participant to enter a number. If the number is beween 0 and the maximum points then the learner will get these points.
The question type is limited to the minimum number of files and class methods needed for a question plugin to work. It adds no extra database table when being installed.
 
Supported features:
  • generic feedback
  • hints and suggested sulutions
  • import and export of questions
  • answer details in an Excel export
  • Tryout in qurestion pool
Missing features:
  • Adjustment of points (post-correction i test)
  • Conditions for competence assignment
Technical Note:
 
Value1 and Value2 and Points correspond to fields in the database table tst_solutions that is written when a participants answer is saved.
  • The posted inputs are saved by:
    • assExampleQuestion::saveWorkingData()
  • The following functions use this data by reading it with assExampleQuestion::getSolutionSubmit() or assExampleQuestion::getSolutionStored()
    • assExampleQuestion::calculateReachedPoints()
    • assExampleQuestion::setExportDetailsXLS()
    • assExampleQuestionGUI::getSolutionOutput()
    • assExampleQuestionGUI::getTestOutput()
Different question types inILIAS use value1 and value2 in a custom way. Only the functions listed above need to know their structure. It is possible that more than one record is stored for an answer (e.g. if the question has parts). Value1 and value2 can for example be used in a key/value style (see assAccountingQuestion plugin).

Optional Extensions

Implementation of iQuestionCondition-Interface

This interface enables the possibility to evaluate test solutions with logical expressions. To have your question type supporting logical expression in the result evaluation, the implementation of iQuestionCondition in your classes/class.<PluginName>.php is required.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Get all available operations for a specific question
*
* @param $expression
*
* @internal param string $expression_type
* @return array
*/

public function getOperators($expression);
 
/**
* Get all available expression types for a specific question
*
* @return array
*/

public function getExpressionTypes();
 
/**
* Get the user solution for a question by active_id and the test pass
*
* @param int $active_id
* @param int $pass
*
* @return ilUserQuestionResult
*/

public function getUserQuestionResult($active_id, $pass);
 
/**
* If index is null, the function returns an array with all anwser options
* Else it returns the specific answer option
*
* @param null|int $index
*
* @return array|ASS_AnswerSimple
*/

public function getAvailableAnswerOptions($index = null);

public function getOperators($expression);

The returned operators depence on the delivered expression. There is another class ./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php that now all useable operators for each Expression. Your code should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Get all available operations for a specific question
*
* @param string $expression
*
* @internal param string $expression_type
* @return array
*/

public function getOperators($expression)
{
require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
return ilOperatorsExpressionMapping::getOperatorsByExpression($expression);
}

public function getExpressionTypes();

This function returns all supported expression of your question type. For non-core question types, it is recommended to only use the two expression types iQuestionCondition::PercentageResultExpression and iQuestionCondition::EmptyAnswerExpression. All available expressions can be found in the iQuestionCondition interface.

public function getUserQuestionResult($active_id, $pass);

This function is used to evaluate the test result based on logical expressions. This function should create a ./Modules/TestQuestionPool/classes/class.ilUserQuuestionResult.phpobject which holds all relevant information about a solution in a test pass.

The following code-example describes how your code should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Get the user solution for a question by active_id and the test pass
*
* @param int $active_id
* @param int $pass
*
* @return ilUserQuestionResult
*/

public function getUserQuestionResult($active_id, $pass)
{
/** @var ilDB $ilDB */
global $ilDB;
$result = new ilUserQuestionResult($this, $active_id, $pass);
 
$maxStep = $this->lookupMaxStep($active_id, $pass);
 
$data = $ilDB->queryF(
"SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
array("integer", "integer", "integer","integer"),
array($active_id, $pass, $this->getId(), $maxStep)
);
 
$row = $ilDB->fetchAssoc($data);
 
if($row != null)
{
++$row["value1"];
$result->addKeyValue($row["value1"], $row["value1"]);
}
 
$points = $this->calculateReachedPoints($active_id, $pass);
$max_points = $this->getMaximumPoints();
 
$result->setReachedPercentage(($points/$max_points) * 100);
 
return $result;
}

The functioncall in line 28 $result->addKeyValue($row["value1"], $row["value1"]) adds a single result entry to the result object. The example-code is taken from the implementation in the sinle-choice question. It must be decided by each question type with attributes are passed to this function. The delivered attributes depends on your questions tst-solutions structure.

In line 34 the reached percentage is determined.

This to function calls are the most important ones. If they are missing, your question won't work probably with logical expressions

public function getAvailableAnswerOptions($index = null)

This function returns all available answer options. If an index has been delivered, the function returns the specific answer option. Notice that the return value of this function can be null.

History

Changed function signature of assQuestionGUI::getPreview()

public function getPreview($show_question_only = FALSE, $showInlineFeedback = false)

This function got a new parameter $showInlineFeedback. The extended GUI class of a plugin must declare this parameter to be compatible.

Additional function getSolutionSubmit()

/**
* Get the submitted user input as a serializable value
*
* @return mixed user input (scalar, object or array)
*/

public function getSolutionSubmit()

This function is not pre-defined in assQuestion but must be present in a question type for "check" functionality on a question pools "preview" screen. It should provide the posted user input in a data structure that can be stored in the user session. The actual data type is not defined  but must be known by the corresponding function calculateReachedPointsforSolution().

Additional function calculateReachedPointsForSolution()

This function is not pre-defined in assQuestion but must be present in a question type for "check" functionality on a question pools "preview" screen. It gets a posted user solution and calculates the reached points for it. The actual data type of solution is not defined but must match the type returned by getSolutionSubmit().

/**
* Calculate the reached points for a submitted user input
*
* @param mixed user input (scalar, object or array)
*/

public function calculateReachedPointsforSolution($solution)