Import/export for questiontype plugins
Moodle1.9 While it is easy enough for import and export formats to support the core question types, it is harder for them to handle any third-party question types that may have been installed. This page describes the mechanism that allows third-party question types to participate in import and export, for formats that support this mechanism. Currently, that means Moodle XML, GIFT and QTI2 formats. QTI2 support is from 1.9.6.
This mechanism was implemented in Moodle 1.9. and back-ported to Moodle 1.8.3+.
New methods in questiontype class
The main mechanism for this process will be new methods for import and export to be added to the questiontype class. There would be a discrete method for each supported format type. Note that these will not (and should not) be implemented for core question types.
Import
Implement a method for each import format that you wish to handle in the questiontype class. The naming convention is import_from_format (for example import_from_gift, import_from_xml). The method is responsible for checking the supplied data and returning a question object suitable for the save_question_options() method in the appropriate questiontype class. The method must check in particular that the data is correct for the questiontype and return a false if it is not. This is because import methods will be polled to find one to handle the data, as the correct question type cannot always be established in the general case.
Warning: It is vital that the function makes some sort of check that the question is valid (for this qtype) and returns false if it is not.
The template for one of these methods would be (using import_from_xml as an example) as follows:
/** * Provide import functionality for xml format * @param data mixed the segment of data containing the question * @param question object question object processed (so far) by standard import code * @param format object the format object so that helper methods can be used (in particular error() ) * @param extra mixed any additional format specific data that may be passed by the format (see format code for info) * @return object question object suitable for save_options() call or false if cannot handle */ function import_from_xml( $data, $question, $format, $extra=null ) { if ($data->....... .... return $question; } else { return false; } }
Notes:
- The $question parameter will contain whatever the core routines have managed to work out so far
- The $format object is the calling object in case there are any useful methods. In particular this method should call $format->error() to report errors properly
Export
For each export format that your question plugin supports write a method called export_to_format (for example, export_to_gift, export_to_xml). The method simply takes the question object (use print_r() to see what it looks like) and returns a string containing the lines to append to the exported file. Returning false will be regarded as an error condition.
The template for one of these methods (using xml format as an example) could be:
/** * Provide export functionality for xml format * @param question object the question object * @param format object the format object so that helper methods can be used * @param extra mixed any additional format specific data that may be passed by the format (see format code for info) * @return string the data to append to the output buffer or false if error */ function export_to_xml( $question, $format, $extra=null ) { .... return $xmlquestion; }
The chances are that the detailed coding will be similar to an existing question type. Study the code in question/format/xml/format.php to see how it works.
New methods in qformat_default class (format.php)
You do not need to read this section to understand adding this functionality to question type plugins. It is included for completeness/history.
Two methods (one for import and one for export) are provided for the format plugin to call to handle situations that they cannot handle themselves. These are called from the format classes (the import/export plugins) in an appropriate place (ie, when they "decide" that they cannot handle the question internally)
Import
As import cannot, in the general sense, matching unhandled question types to the classes (as names may well differ) the installed questiontypes will be polled to find the (first) one that will handle the data. The import/export format should simply call $this->import_plugin() when it is unable to handle the question data itself. It will either return the $question object or false if it fails. The format class retains the responsibility of reporting unhandled question types. This functionality is optional for import/output formats.
/** * Import for questiontype plugins * @param data mixed the segment of data containing the question * @param question object question object processed (so far) by standard import code * @param extra mixed any additional format specific data that may be passed by the format (see format code for info) * @return object question object suitable for save_options() call or false if cannot handle */ function try_importing_using_qtypes( $data, $question, $extra=null ) { global $QTYPES; $formatname = substr(get_class( $this ), length('qformat_')); $methodname = "import_from_$formatname"; foreach ($QTYPES as $qtype) { if (method_exists( $qtype, $methodname )) { if ($question = $qtype->$methodname( $data, $question, $this, $extra )) { return $question; } } } return false; }
Export
The export routine simpler, as the questiontype name will always be known. If the export routine encounters a question type that it cannot handle it should call $this->export_plugin(). If the question is handled the function will return a string containing the exported data. If it cannot be handled the function returns false.
/** * Provide export functionality for plugin questiontypes * @param name questiontype name * @param question object the question object * @param extra mixed any additional format specific data that may be passed by the format (see format code for info) * @return string the data to append to the output buffer or false if error */ function try_exporting_using_qtypes( $name, $question, $extra=null ) { global $QTYPES; $formatname = substr(get_class( $this ), length('qformat_')); $methodname = "export_to_$formatname"; if (array_key_exists( $name, $QTYPES )) { $qtype = $QTYPES[ $name ]; if (method_exists( $qtype, $methodname )) { if ( $data = $qtype->$methodname( $question, $this, $extra )) { return $data; } } } return false; }
Error handling
The recommended way for import/export formats to report a syntax error is to call the error() method within the format class. The format object is passed to the questiontype method so that $format->error can be called.
Notes for individual formats
This is currently only implemented for the XML and GIFT formats. These are specific notes about how they are implemented and what to expect in the passed parameters.
Moodle XML Format
Import
For import, try_exporting_using_qtype() is called if the questiontype (as read from the xml file) is not recognised as a core type. The parameters will be set as follows:
- $data = the question object
- $question = null
- $extra = null
Note that only the question object is passed. The first thing you probably need to do is to test for the questiontype name, thus...
$qtype = $data['@']['type'];
You should test this to see if you want to handle it and return false if not. It is important that you do this step!
Next you will want to parse the headers, thus...
$question = $format->import_headers( $data );
The remainder will be specific to your question type plugin.
Export
For export your 'export_to_xml' method will be called correctly if it exists and an instance of the question type exists. The parameters are set as follows:
- $question = the question object
- $format = reference to the qformat_xml object. You can use this to call methods on that object if required.
- $extra = is not used
NOTE: The 'headers' part of the question (the name and question text etc.) will already have been generated prior to this function being called so you do not have to do that.
GIFT Format
For import GIFT is unusual in that the question type cannot be established until the question has been completely parsed. Unlike other formats where try_importing_using_qtypes() is called at the "end" where it has been established that no built in handler exists, GIFT makes this call before built in question handlers are considered. This makes it doubly important that the plugin code checks that the data is appropriate for the qtype and returns false if not.
The contents of the parameters are as follows:
- $data = the complete text of the question (in a single string)
- $question = the current question object. You can expect to find the common parts of the question already parsed and included as fields (e.g., name)
- $extra = the part of the question between the braces ( {..} ) if it exists (in a single string)
For export GIFT is much more as expected. If the questiontype is not handled a call is made to try_exporting_using_qtypes() with the standard parameters.
QTI2
The mode of operation and the parameters are exactly the same as the Moodle XML format. QTI support was added leading up to 1.9.6
Adding Support To Import/Export Plugins
You can, of course, add support for qtype plugins to import/export plugins. You will need to add calls to try_exporting_using_qtypes() and/or try_importing_using_qtypes() as appropriate. Particularly for import, you should consider what is the optimal data to pass as parameters and document that somewhere so qtype plugin writers know what to expect.