mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 08:26:37 +02:00
MDL-20636 converstion of questionlib.php and base questiontype.php, plus other cheanges required to get the question editing page to display.
This commit is contained in:
parent
06f8ed54fd
commit
f29aeb5afd
13 changed files with 3165 additions and 2639 deletions
|
@ -14,12 +14,14 @@
|
|||
|
||||
admin_externalpage_setup('manageqtypes');
|
||||
|
||||
$qtypes = question_bank::get_all_qtypes();
|
||||
|
||||
/// Get some data we will need - question counts and which types are needed.
|
||||
$counts = $DB->get_records_sql("
|
||||
SELECT qtype, COUNT(1) as numquestions, SUM(hidden) as numhidden
|
||||
FROM {question} GROUP BY qtype", array());
|
||||
$needed = array();
|
||||
foreach ($QTYPES as $qtypename => $qtype) {
|
||||
foreach ($qtypes as $qtypename => $qtype) {
|
||||
if (!isset($counts[$qtypename])) {
|
||||
$counts[$qtypename] = new stdClass;
|
||||
$counts[$qtypename]->numquestions = 0;
|
||||
|
@ -29,13 +31,13 @@
|
|||
$counts[$qtypename]->numquestions -= $counts[$qtypename]->numhidden;
|
||||
}
|
||||
$needed['missingtype'] = true; // The system needs the missing question type.
|
||||
foreach ($QTYPES as $qtypename => $qtype) {
|
||||
foreach ($qtypes as $qtypename => $qtype) {
|
||||
foreach ($qtype->requires_qtypes() as $reqtype) {
|
||||
$needed[$reqtype] = true;
|
||||
}
|
||||
}
|
||||
foreach ($counts as $qtypename => $count) {
|
||||
if (!isset($QTYPES[$qtypename])) {
|
||||
if (!isset($qtypes[$qtypename])) {
|
||||
$counts['missingtype']->numquestions += $count->numquestions - $count->numhidden;
|
||||
$counts['missingtype']->numhidden += $count->numhidden;
|
||||
}
|
||||
|
@ -44,7 +46,7 @@
|
|||
/// Work of the correct sort order.
|
||||
$config = get_config('question');
|
||||
$sortedqtypes = array();
|
||||
foreach ($QTYPES as $qtypename => $qtype) {
|
||||
foreach ($qtypes as $qtypename => $qtype) {
|
||||
$sortedqtypes[$qtypename] = $qtype->local_name();
|
||||
}
|
||||
$sortedqtypes = question_sort_qtype_array($sortedqtypes, $config);
|
||||
|
@ -53,7 +55,7 @@
|
|||
|
||||
// Disable.
|
||||
if (($disable = optional_param('disable', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
|
||||
if (!isset($QTYPES[$disable])) {
|
||||
if (!isset($qtypes[$disable])) {
|
||||
print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $disable);
|
||||
}
|
||||
|
||||
|
@ -63,11 +65,11 @@
|
|||
|
||||
// Enable.
|
||||
if (($enable = optional_param('enable', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
|
||||
if (!isset($QTYPES[$enable])) {
|
||||
if (!isset($qtypes[$enable])) {
|
||||
print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $enable);
|
||||
}
|
||||
|
||||
if (!$QTYPES[$enable]->menu_name()) {
|
||||
if (!$qtypes[$enable]->menu_name()) {
|
||||
print_error('cannotenable', 'question', admin_url('qtypes.php'), $enable);
|
||||
}
|
||||
|
||||
|
@ -77,7 +79,7 @@
|
|||
|
||||
// Move up in order.
|
||||
if (($up = optional_param('up', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
|
||||
if (!isset($QTYPES[$up])) {
|
||||
if (!isset($qtypes[$up])) {
|
||||
print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $up);
|
||||
}
|
||||
|
||||
|
@ -88,7 +90,7 @@
|
|||
|
||||
// Move down in order.
|
||||
if (($down = optional_param('down', '', PARAM_SAFEDIR)) && confirm_sesskey()) {
|
||||
if (!isset($QTYPES[$down])) {
|
||||
if (!isset($qtypes[$down])) {
|
||||
print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $down);
|
||||
}
|
||||
|
||||
|
@ -104,11 +106,11 @@
|
|||
print_error('cannotdeletemissingqtype', 'admin', admin_url('qtypes.php'));
|
||||
}
|
||||
|
||||
if (!isset($QTYPES[$delete])) {
|
||||
if (!isset($qtypes[$delete])) {
|
||||
print_error('unknownquestiontype', 'question', admin_url('qtypes.php'), $delete);
|
||||
}
|
||||
|
||||
$qtypename = $QTYPES[$delete]->local_name();
|
||||
$qtypename = $qtypes[$delete]->local_name();
|
||||
if ($counts[$delete]->numquestions + $counts[$delete]->numhidden > 0) {
|
||||
print_error('cannotdeleteqtypeinuse', 'admin', admin_url('qtypes.php'), $qtypename);
|
||||
}
|
||||
|
@ -119,7 +121,7 @@
|
|||
|
||||
// If not yet confirmed, display a confirmation message.
|
||||
if (!optional_param('confirm', '', PARAM_BOOL)) {
|
||||
$qtypename = $QTYPES[$delete]->local_name();
|
||||
$qtypename = $qtypes[$delete]->local_name();
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('deleteqtypeareyousure', 'admin', $qtypename));
|
||||
echo $OUTPUT->confirm(get_string('deleteqtypeareyousuremessage', 'admin', $qtypename),
|
||||
|
@ -141,13 +143,13 @@
|
|||
unset_config($delete . '_sortorder', 'question');
|
||||
|
||||
// Then the tables themselves
|
||||
drop_plugin_tables($delete, $QTYPES[$delete]->plugin_dir() . '/db/install.xml', false);
|
||||
drop_plugin_tables($delete, $qtypes[$delete]->plugin_dir() . '/db/install.xml', false);
|
||||
|
||||
// Remove event handlers and dequeue pending events
|
||||
events_uninstall('qtype/' . $delete);
|
||||
|
||||
$a->qtype = $qtypename;
|
||||
$a->directory = $QTYPES[$delete]->plugin_dir();
|
||||
$a->directory = $qtypes[$delete]->plugin_dir();
|
||||
echo $OUTPUT->box(get_string('qtypedeletefiles', 'admin', $a), 'generalbox', 'notice');
|
||||
echo $OUTPUT->continue_button(admin_url('qtypes.php'));
|
||||
echo $OUTPUT->footer();
|
||||
|
@ -174,7 +176,7 @@
|
|||
/// Add a row for each question type.
|
||||
$createabletypes = question_type_menu();
|
||||
foreach ($sortedqtypes as $qtypename => $localname) {
|
||||
$qtype = $QTYPES[$qtypename];
|
||||
$qtype = $qtypes[$qtypename];
|
||||
$row = array();
|
||||
|
||||
// Question icon and name.
|
||||
|
@ -213,7 +215,7 @@
|
|||
$strtypes = array();
|
||||
if (!empty($requiredtypes)) {
|
||||
foreach ($requiredtypes as $required) {
|
||||
$strtypes[] = $QTYPES[$required]->local_name();
|
||||
$strtypes[] = $qtypes[$required]->local_name();
|
||||
}
|
||||
$row[] = implode(', ', $strtypes);
|
||||
} else {
|
||||
|
|
1794
lib/questionlib.php
1794
lib/questionlib.php
File diff suppressed because it is too large
Load diff
|
@ -63,8 +63,9 @@ abstract class question_bank {
|
|||
if (isset(self::$questiontypes[$qtypename])) {
|
||||
return self::$questiontypes[$qtypename];
|
||||
}
|
||||
$file = $CFG->dirroot . '/question/type/' . $qtypename . '/questiontype.php';
|
||||
$file = get_plugin_directory('qtype', $qtypename) . '/questiontype.php';
|
||||
if (!is_readable($file)) {
|
||||
echo 'problem';
|
||||
if ($mustexist || $qtypename == 'missingtype') {
|
||||
throw new Exception('Unknown question type ' . $qtypename);
|
||||
} else {
|
||||
|
@ -73,6 +74,9 @@ abstract class question_bank {
|
|||
}
|
||||
include_once($file);
|
||||
$class = 'qtype_' . $qtypename;
|
||||
if (!class_exists($class)) {
|
||||
throw new coding_exception("Class $class must be defined in $file");
|
||||
}
|
||||
self::$questiontypes[$qtypename] = new $class();
|
||||
return self::$questiontypes[$qtypename];
|
||||
}
|
||||
|
@ -82,7 +86,7 @@ abstract class question_bank {
|
|||
* @return boolean whether users are allowed to create questions of this type.
|
||||
*/
|
||||
public static function qtype_enabled($qtypename) {
|
||||
;
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,9 +102,12 @@ abstract class question_bank {
|
|||
*/
|
||||
public static function get_all_qtypes() {
|
||||
$qtypes = array();
|
||||
$plugins = get_list_of_plugins('question/type', 'datasetdependent');
|
||||
foreach ($plugins as $plugin) {
|
||||
foreach (get_plugin_list('qtype') as $plugin => $notused) {
|
||||
try {
|
||||
$qtypes[$plugin] = self::get_qtype($plugin);
|
||||
} catch (Exception $e) {
|
||||
// TODO ingore, but reivew this later.
|
||||
}
|
||||
}
|
||||
return $qtypes;
|
||||
}
|
||||
|
|
|
@ -551,21 +551,32 @@ abstract class question_flags {
|
|||
}
|
||||
|
||||
public static function initialise_js() {
|
||||
global $CFG;
|
||||
|
||||
require_js(array('yui_yahoo','yui_dom','yui_event','yui_connection'));
|
||||
require_js($CFG->wwwroot . '/question/qengine.js');
|
||||
|
||||
$config = array(
|
||||
'actionurl' => $CFG->wwwroot . '/question/toggleflag.php',
|
||||
'flagicon' => $CFG->pixpath . '/i/flagged.png',
|
||||
'unflagicon' => $CFG->pixpath . '/i/unflagged.png',
|
||||
'flagtooltip' => get_string('clicktoflag', 'question'),
|
||||
'unflagtooltip' => get_string('clicktounflag', 'question'),
|
||||
'flaggedalt' => get_string('flagged', 'question'),
|
||||
'unflaggedalt' => get_string('notflagged', 'question'),
|
||||
global $CFG, $PAGE, $OUTPUT;
|
||||
static $done = false;
|
||||
if ($done) {
|
||||
return;
|
||||
}
|
||||
$module = array(
|
||||
'name' => 'core_question_flags',
|
||||
'fullpath' => '/question/flags.js',
|
||||
'requires' => array('base', 'dom', 'event-delegate', 'io-base'),
|
||||
);
|
||||
return print_js_config($config, 'qengine_config', true);
|
||||
$actionurl = $CFG->wwwroot . '/question/toggleflag.php';
|
||||
$flagattributes = array(
|
||||
0 => array(
|
||||
'src' => $OUTPUT->pix_url('i/unflagged') . '',
|
||||
'title' => get_string('clicktoflag', 'question'),
|
||||
'alt' => get_string('notflagged', 'question'),
|
||||
),
|
||||
1 => array(
|
||||
'src' => $OUTPUT->pix_url('i/flagged') . '',
|
||||
'title' => get_string('clicktounflag', 'question'),
|
||||
'alt' => get_string('flagged', 'question'),
|
||||
),
|
||||
);
|
||||
$PAGE->requires->js_init_call('M.core_question_flags.init',
|
||||
array($actionurl, $flagattributes), false, $module);
|
||||
$done = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@ class core_question_renderer extends renderer_base {
|
|||
'<input type="checkbox" id="' . $id . 'checkbox" name="' . $id . '" value="1" ' . $checked . ' />' .
|
||||
'<input type="hidden" value="' . s($postdata) . '" class="questionflagpostdata" />' .
|
||||
'<label id="' . $id . 'label" for="' . $id . '">' . $this->get_flag_html(
|
||||
$qa->is_flagged(), $id . 'img') . '</label>' . "\n" .
|
||||
$qa->is_flagged(), $id . 'img') . '</label>' . "\n";
|
||||
break;
|
||||
default:
|
||||
$flagcontent = '';
|
||||
|
|
|
@ -52,7 +52,7 @@ Internal changes
|
|||
lang/en_utf8/quiz_regrade.php | 7 -
|
||||
lang/en_utf8/quiz_responses.php | 11 -
|
||||
|
||||
lib/questionlib.php | 1434 ++--------
|
||||
DONE lib/questionlib.php | 1434 ++--------
|
||||
|
||||
mod/quiz/accessrules.php | 828 ++++++
|
||||
mod/quiz/attempt.php | 742 ++----
|
||||
|
@ -158,7 +158,7 @@ DONE question/comment.html | 25 -
|
|||
DONE question/editlib.php | 36 +-
|
||||
GONE question/exportfile.php | 52 +-
|
||||
DONE question/file.php | 171 +- | but this file is probably obsolete.
|
||||
!!!TODO question/import_form.php | 19 + | the change is to add validation that a file has been uploaded.
|
||||
question/import_form.php | 19 + | the change is to add validation that a file has been uploaded.
|
||||
DONE question/move_form.php | 32 +-
|
||||
DONE question/preview.js | 47 +
|
||||
question/preview.php | 408 ++--
|
||||
|
@ -168,6 +168,14 @@ DONE question/question.php | 3 +-
|
|||
question/restorelib.php | 88 +-
|
||||
DONE question/toggleflag.php | 49 +
|
||||
|
||||
question/type/edit_question_form.php | 264 ++-
|
||||
question/type/question.html | 46 -
|
||||
question/type/questionbase.php | 787 ++++++
|
||||
DONE question/type/questiontype.php | 1302 ++-------
|
||||
question/type/rendererbase.php | 265 ++ -- TODO diff questointype to get necessary changes.
|
||||
question/type/simpletest/testquestionbase.php | 117 +
|
||||
DONE question/type/simpletest/testquestiontype.php | 91 +-
|
||||
|
||||
question/behaviour/behaviourbase.php | 627 +++++
|
||||
question/behaviour/rendererbase.php | 200 ++
|
||||
|
||||
|
@ -423,14 +431,6 @@ DONE question/toggleflag.php | 49 +
|
|||
question/type/truefalse/simpletest/testquestiontype.php | 73 +
|
||||
question/type/truefalse/version.php | 4 +-
|
||||
|
||||
question/type/edit_question_form.php | 264 ++-
|
||||
question/type/question.html | 46 -
|
||||
question/type/questionbase.php | 787 ++++++
|
||||
question/type/questiontype.php | 1302 ++-------
|
||||
question/type/rendererbase.php | 265 ++
|
||||
question/type/simpletest/testquestionbase.php | 117 +
|
||||
question/type/simpletest/testquestiontype.php | 91 +-
|
||||
|
||||
theme/standard/styles_color.css | 72 +-
|
||||
theme/standard/styles_fonts.css | 20 +-
|
||||
theme/standard/styles_layout.css | 265 ++-
|
||||
|
|
807
question/todo/questionlib_2.0.diff.txt
Normal file
807
question/todo/questionlib_2.0.diff.txt
Normal file
|
@ -0,0 +1,807 @@
|
|||
|
||||
/**
|
||||
* Prints a question
|
||||
*
|
||||
* Simply calls the question type specific print_question() method.
|
||||
+ *
|
||||
+ * @global array
|
||||
* @param object $question The question to be rendered.
|
||||
* @param object $state The state to render the question in.
|
||||
* @param integer $number The number for this question.
|
||||
* @param object $cmoptions The options specified by the course module
|
||||
* @param object $options An object specifying the rendering options.
|
||||
*/
|
||||
-function print_question(&$question, &$state, $number, $cmoptions, $options=null) {
|
||||
+function print_question(&$question, &$state, $number, $cmoptions, $options=null, $context=null) {
|
||||
global $QTYPES;
|
||||
- $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options);
|
||||
+ $QTYPES[$question->qtype]->print_question($question, $state, $number, $cmoptions, $options, $context);
|
||||
}
|
||||
/**
|
||||
* Saves question options
|
||||
*
|
||||
* Simply calls the question type specific save_question_options() method.
|
||||
+ *
|
||||
+ * @global array
|
||||
*/
|
||||
function save_question_options($question) {
|
||||
global $QTYPES;
|
||||
@@ -2075,8 +2255,9 @@ function sort_categories_by_tree(&$categories, $id = 0, $level = 1) {
|
||||
//If level = 1, we have finished, try to look for non processed categories (bad parent) and sort them too
|
||||
if ($level == 1) {
|
||||
foreach ($keys as $key) {
|
||||
- //If not processed and it's a good candidate to start (because its parent doesn't exist in the course)
|
||||
- if (!isset($categories[$key]->processed) && !$DB->record_exists('question_categories', array('course'=>$categories[$key]->course, 'id'=>$categories[$key]->parent))) {
|
||||
+ // If not processed and it's a good candidate to start (because its parent doesn't exist in the course)
|
||||
+ if (!isset($categories[$key]->processed) && !$DB->record_exists(
|
||||
+ 'question_categories', array('contextid'=>$categories[$key]->contextid, 'id'=>$categories[$key]->parent))) {
|
||||
$children[$key] = $categories[$key];
|
||||
$categories[$key]->processed = true;
|
||||
$children = $children + sort_categories_by_tree($categories, $children[$key]->id, $level+1);
|
||||
@@ -2167,16 +2348,23 @@ function add_indented_names($categories, $nochildrenof = -1) {
|
||||
* @param integer $selected optionally, the id of a category to be selected by default in the dropdown.
|
||||
*/
|
||||
function question_category_select_menu($contexts, $top = false, $currentcat = 0, $selected = "", $nochildrenof = -1) {
|
||||
+ global $OUTPUT;
|
||||
$categoriesarray = question_category_options($contexts, $top, $currentcat, false, $nochildrenof);
|
||||
if ($selected) {
|
||||
- $nothing = '';
|
||||
+ $choose = '';
|
||||
} else {
|
||||
- $nothing = 'choose';
|
||||
+ $choose = 'choosedots';
|
||||
+ }
|
||||
+ $options = array();
|
||||
+ foreach($categoriesarray as $group=>$opts) {
|
||||
+ $options[] = array($group=>$opts);
|
||||
}
|
||||
- choose_from_menu_nested($categoriesarray, 'category', $selected, $nothing);
|
||||
+
|
||||
+ echo html_writer::select($options, 'category', $selected, $choose);
|
||||
}
|
||||
@@ -2216,23 +2406,31 @@ function question_edit_url($context) {
|
||||
/**
|
||||
* Gets the default category in the most specific context.
|
||||
* If no categories exist yet then default ones are created in all contexts.
|
||||
*
|
||||
+ * @global object
|
||||
* @param array $contexts The context objects for this context and all parent contexts.
|
||||
* @return object The default category - the category in the course context
|
||||
*/
|
||||
function question_make_default_categories($contexts) {
|
||||
global $DB;
|
||||
+ static $preferredlevels = array(
|
||||
+ CONTEXT_COURSE => 4,
|
||||
+ CONTEXT_MODULE => 3,
|
||||
+ CONTEXT_COURSECAT => 2,
|
||||
+ CONTEXT_SYSTEM => 1,
|
||||
+ );
|
||||
|
||||
$toreturn = null;
|
||||
+ $preferredness = 0;
|
||||
// If it already exists, just return it.
|
||||
foreach ($contexts as $key => $context) {
|
||||
- if (!$exists = $DB->record_exists("question_categories", array('contextid'=>$context->id))){
|
||||
+ if (!$exists = $DB->record_exists("question_categories", array('contextid'=>$context->id))) {
|
||||
// Otherwise, we need to make one
|
||||
$category = new stdClass;
|
||||
$contextname = print_context_name($context, false, true);
|
||||
@@ -2242,19 +2440,20 @@ function question_make_default_categories($contexts) {
|
||||
$category->parent = 0;
|
||||
$category->sortorder = 999; // By default, all categories get this number, and are sorted alphabetically.
|
||||
$category->stamp = make_unique_id_code();
|
||||
- if (!$category->id = $DB->insert_record('question_categories', $category)) {
|
||||
- print_error('cannotcreatedefaultcat', '', '', print_context_name($context));
|
||||
- }
|
||||
+ $category->id = $DB->insert_record('question_categories', $category);
|
||||
} else {
|
||||
$category = question_get_default_category($context->id);
|
||||
}
|
||||
-
|
||||
- if ($context->contextlevel == CONTEXT_COURSE){
|
||||
- $toreturn = clone($category);
|
||||
+ if ($preferredlevels[$context->contextlevel] > $preferredness &&
|
||||
+ has_any_capability(array('moodle/question:usemine', 'moodle/question:useall'), $context)) {
|
||||
+ $toreturn = $category;
|
||||
+ $preferredness = $preferredlevels[$context->contextlevel];
|
||||
}
|
||||
}
|
||||
|
||||
-
|
||||
+ if (!is_null($toreturn)) {
|
||||
+ $toreturn = clone($toreturn);
|
||||
+ }
|
||||
return $toreturn;
|
||||
}
|
||||
|
||||
@@ -2313,9 +2514,12 @@ function question_category_options($contexts, $top = false, $currentcat = 0, $po
|
||||
if ($popupform){
|
||||
$popupcats = array();
|
||||
foreach ($categoriesarray as $contextstring => $optgroup){
|
||||
- $popupcats[] = '--'.$contextstring;
|
||||
- $popupcats = array_merge($popupcats, $optgroup);
|
||||
- $popupcats[] = '--';
|
||||
+ $group = array();
|
||||
+ foreach ($optgroup as $key=>$value) {
|
||||
+ $key = str_replace($CFG->wwwroot, '', $key);
|
||||
+ $group[$key] = $value;
|
||||
+ }
|
||||
+ $popupcats[] = array($contextstring=>$group);
|
||||
}
|
||||
return $popupcats;
|
||||
} else {
|
||||
@@ -2335,7 +2539,7 @@ function question_add_context_in_key($categories){
|
||||
function question_add_tops($categories, $pcontexts){
|
||||
$topcats = array();
|
||||
foreach ($pcontexts as $context){
|
||||
- $newcat = new object();
|
||||
+ $newcat = new stdClass();
|
||||
$newcat->id = "0,$context";
|
||||
$newcat->name = get_string('top');
|
||||
$newcat->parent = -1;
|
||||
function get_import_export_formats( $type ) {
|
||||
|
||||
global $CFG;
|
||||
- $fileformats = get_list_of_plugins("question/format");
|
||||
+ $fileformats = get_plugin_list("qformat");
|
||||
|
||||
$fileformatname=array();
|
||||
require_once( "{$CFG->dirroot}/question/format.php" );
|
||||
- foreach ($fileformats as $key => $fileformat) {
|
||||
- $format_file = $CFG->dirroot . "/question/format/$fileformat/format.php";
|
||||
- if (file_exists( $format_file ) ) {
|
||||
- require_once( $format_file );
|
||||
+ foreach ($fileformats as $fileformat=>$fdir) {
|
||||
+ $format_file = "$fdir/format.php";
|
||||
+ if (file_exists($format_file) ) {
|
||||
+ require_once($format_file);
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
@@ -2400,7 +2607,10 @@ function get_import_export_formats( $type ) {
|
||||
if ($provided) {
|
||||
$formatname = get_string($fileformat, 'quiz');
|
||||
if ($formatname == "[[$fileformat]]") {
|
||||
- $formatname = $fileformat; // Just use the raw folder name
|
||||
+ $formatname = get_string($fileformat, 'qformat_'.$fileformat);
|
||||
+ if ($formatname == "[[$fileformat]]") {
|
||||
+ $formatname = $fileformat; // Just use the raw folder name
|
||||
+ }
|
||||
}
|
||||
$fileformatnames[$fileformat] = $formatname;
|
||||
}
|
||||
@@ -2412,50 +2622,39 @@ function get_import_export_formats( $type ) {
|
||||
|
||||
|
||||
/**
|
||||
-* Create default export filename
|
||||
-*
|
||||
-* @return string default export filename
|
||||
-* @param object $course
|
||||
-* @param object $category
|
||||
+* Create a reasonable default file name for exporting questions from a particular
|
||||
+* category.
|
||||
+* @param object $course the course the questions are in.
|
||||
+* @param object $category the question category.
|
||||
+* @return string the filename.
|
||||
*/
|
||||
-function default_export_filename($course,$category) {
|
||||
- //Take off some characters in the filename !!
|
||||
- $takeoff = array(" ", ":", "/", "\\", "|");
|
||||
- $export_word = str_replace($takeoff,"_",moodle_strtolower(get_string("exportfilename","quiz")));
|
||||
- //If non-translated, use "export"
|
||||
- if (substr($export_word,0,1) == "[") {
|
||||
- $export_word= "export";
|
||||
- }
|
||||
-
|
||||
- //Calculate the date format string
|
||||
- $export_date_format = str_replace(" ","_",get_string("exportnameformat","quiz"));
|
||||
- //If non-translated, use "%Y%m%d-%H%M"
|
||||
- if (substr($export_date_format,0,1) == "[") {
|
||||
- $export_date_format = "%%Y%%m%%d-%%H%%M";
|
||||
- }
|
||||
-
|
||||
- //Calculate the shortname
|
||||
- $export_shortname = clean_filename($course->shortname);
|
||||
- if (empty($export_shortname) or $export_shortname == '_' ) {
|
||||
- $export_shortname = $course->id;
|
||||
- }
|
||||
-
|
||||
- //Calculate the category name
|
||||
- $export_categoryname = clean_filename($category->name);
|
||||
-
|
||||
- //Calculate the final export filename
|
||||
- //The export word
|
||||
- $export_name = $export_word."-";
|
||||
- //The shortname
|
||||
- $export_name .= moodle_strtolower($export_shortname)."-";
|
||||
- //The category name
|
||||
- $export_name .= moodle_strtolower($export_categoryname)."-";
|
||||
- //The date format
|
||||
- $export_name .= userdate(time(),$export_date_format,99,false);
|
||||
- //Extension is supplied by format later.
|
||||
+function question_default_export_filename($course, $category) {
|
||||
+ // We build a string that is an appropriate name (questions) from the lang pack,
|
||||
+ // then the corse shortname, then the question category name, then a timestamp.
|
||||
+
|
||||
+ $base = clean_filename(get_string('exportfilename', 'question'));
|
||||
+
|
||||
+ $dateformat = str_replace(' ', '_', get_string('exportnameformat', 'question'));
|
||||
+ $timestamp = clean_filename(userdate(time(), $dateformat, 99, false));
|
||||
+
|
||||
+ $shortname = clean_filename($course->shortname);
|
||||
+ if ($shortname == '' || $shortname == '_' ) {
|
||||
+ $shortname = $course->id;
|
||||
+ }
|
||||
+
|
||||
+ $categoryname = clean_filename(format_string($category->name));
|
||||
+
|
||||
+ return "{$base}-{$shortname}-{$categoryname}-{$timestamp}";
|
||||
|
||||
return $export_name;
|
||||
}
|
||||
+
|
||||
+/**
|
||||
+ * @package moodlecore
|
||||
+ * @subpackage question
|
||||
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
+ */
|
||||
class context_to_string_translator{
|
||||
/**
|
||||
* @var array used to translate between contextids and strings for this context.
|
||||
@@ -2549,13 +2751,13 @@ function question_has_capability_on($question, $cap, $cachecat = -1){
|
||||
static $questions = array();
|
||||
static $categories = array();
|
||||
static $cachedcat = array();
|
||||
- if ($cachecat != -1 && (array_search($cachecat, $cachedcat)===FALSE)){
|
||||
- $questions += $DB->get_records('question', array('category'=>$cachecat));
|
||||
+ if ($cachecat != -1 && array_search($cachecat, $cachedcat) === false) {
|
||||
+ $questions += $DB->get_records('question', array('category' => $cachecat));
|
||||
$cachedcat[] = $cachecat;
|
||||
}
|
||||
if (!is_object($question)){
|
||||
if (!isset($questions[$question])){
|
||||
- if (!$questions[$question] = $DB->get_record('question', array('id'=>$question), 'id,category,createdby')) {
|
||||
+ if (!$questions[$question] = $DB->get_record('question', array('id' => $question), 'id,category,createdby')) {
|
||||
print_error('questiondoesnotexist', 'question');
|
||||
}
|
||||
}
|
||||
@@ -2567,11 +2769,12 @@ function question_has_capability_on($question, $cap, $cachecat = -1){
|
||||
}
|
||||
}
|
||||
$category = $categories[$question->category];
|
||||
+ $context = get_context_instance_by_id($category->contextid);
|
||||
|
||||
if (array_search($cap, $question_questioncaps)!== FALSE){
|
||||
- if (!has_capability('moodle/question:'.$cap.'all', get_context_instance_by_id($category->contextid))){
|
||||
+ if (!has_capability('moodle/question:'.$cap.'all', $context)){
|
||||
if ($question->createdby == $USER->id){
|
||||
- return has_capability('moodle/question:'.$cap.'mine', get_context_instance_by_id($category->contextid));
|
||||
+ return has_capability('moodle/question:'.$cap.'mine', $context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -2579,7 +2782,7 @@ function question_has_capability_on($question, $cap, $cachecat = -1){
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
- return has_capability('moodle/question:'.$cap, get_context_instance_by_id($category->contextid));
|
||||
+ return has_capability('moodle/question:'.$cap, $context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2594,107 +2797,6 @@ function question_require_capability_on($question, $cap){
|
||||
return true;
|
||||
}
|
||||
|
||||
-function question_file_links_base_url($courseid){
|
||||
- global $CFG;
|
||||
- $baseurl = preg_quote("$CFG->wwwroot/file.php", '!');
|
||||
- $baseurl .= '('.preg_quote('?file=', '!').')?';//may or may not
|
||||
- //be using slasharguments, accept either
|
||||
- $baseurl .= "/$courseid/";//course directory
|
||||
- return $baseurl;
|
||||
-}
|
||||
-
|
||||
-/*
|
||||
- * Find all course / site files linked to in a piece of html.
|
||||
- * @param string html the html to search
|
||||
- * @param int course search for files for courseid course or set to siteid for
|
||||
- * finding site files.
|
||||
- * @return array files with keys being files.
|
||||
- */
|
||||
-function question_find_file_links_from_html($html, $courseid){
|
||||
- global $CFG;
|
||||
- $baseurl = question_file_links_base_url($courseid);
|
||||
- $searchfor = '!'.
|
||||
- '(<\s*(a|img)\s[^>]*(href|src)\s*=\s*")'.$baseurl.'([^"]*)"'.
|
||||
- '|'.
|
||||
- '(<\s*(a|img)\s[^>]*(href|src)\s*=\s*\')'.$baseurl.'([^\']*)\''.
|
||||
- '!i';
|
||||
- $matches = array();
|
||||
- $no = preg_match_all($searchfor, $html, $matches);
|
||||
- if ($no){
|
||||
- $rawurls = array_filter(array_merge($matches[5], $matches[10]));//array_filter removes empty elements
|
||||
- //remove any links that point somewhere they shouldn't
|
||||
- foreach (array_keys($rawurls) as $rawurlkey){
|
||||
- if (!$cleanedurl = question_url_check($rawurls[$rawurlkey])){
|
||||
- unset($rawurls[$rawurlkey]);
|
||||
- } else {
|
||||
- $rawurls[$rawurlkey] = $cleanedurl;
|
||||
- }
|
||||
-
|
||||
- }
|
||||
- $urls = array_flip($rawurls);// array_flip removes duplicate files
|
||||
- // and when we merge arrays will continue to automatically remove duplicates
|
||||
- } else {
|
||||
- $urls = array();
|
||||
- }
|
||||
- return $urls;
|
||||
-}
|
||||
-
|
||||
-/**
|
||||
- * Check that url doesn't point anywhere it shouldn't
|
||||
- *
|
||||
- * @param $url string relative url within course files directory
|
||||
- * @return mixed boolean false if not OK or cleaned URL as string if OK
|
||||
- */
|
||||
-function question_url_check($url){
|
||||
- global $CFG;
|
||||
- if ((substr(strtolower($url), 0, strlen($CFG->moddata)) == strtolower($CFG->moddata)) ||
|
||||
- (substr(strtolower($url), 0, 10) == 'backupdata')){
|
||||
- return false;
|
||||
- } else {
|
||||
- return clean_param($url, PARAM_PATH);
|
||||
- }
|
||||
-}
|
||||
-
|
||||
-/**
|
||||
- * Find all course / site files linked to in a piece of html.
|
||||
- * @param string html the html to search
|
||||
- * @param int course search for files for courseid course or set to siteid for
|
||||
- * finding site files.
|
||||
- * @return array files with keys being files.
|
||||
- */
|
||||
-function question_replace_file_links_in_html($html, $fromcourseid, $tocourseid, $url, $destination, &$changed){
|
||||
- global $CFG;
|
||||
- require_once($CFG->libdir .'/filelib.php');
|
||||
- $tourl = get_file_url("$tocourseid/$destination");
|
||||
- $fromurl = question_file_links_base_url($fromcourseid).preg_quote($url, '!');
|
||||
- $searchfor = array('!(<\s*(a|img)\s[^>]*(href|src)\s*=\s*")'.$fromurl.'(")!i',
|
||||
- '!(<\s*(a|img)\s[^>]*(href|src)\s*=\s*\')'.$fromurl.'(\')!i');
|
||||
- $newhtml = preg_replace($searchfor, '\\1'.$tourl.'\\5', $html);
|
||||
- if ($newhtml != $html){
|
||||
- $changed = true;
|
||||
- }
|
||||
- return $newhtml;
|
||||
-}
|
||||
-
|
||||
-function get_filesdir_from_context($context){
|
||||
- global $DB;
|
||||
-
|
||||
- switch ($context->contextlevel){
|
||||
- case CONTEXT_COURSE :
|
||||
- $courseid = $context->instanceid;
|
||||
- break;
|
||||
- case CONTEXT_MODULE :
|
||||
- $courseid = $DB->get_field('course_modules', 'course', array('id'=>$context->instanceid));
|
||||
- break;
|
||||
- case CONTEXT_COURSECAT :
|
||||
- case CONTEXT_SYSTEM :
|
||||
- $courseid = SITEID;
|
||||
- break;
|
||||
- default :
|
||||
- print_error('invalidcontext');
|
||||
- }
|
||||
- return $courseid;
|
||||
-}
|
||||
/**
|
||||
* Get the real state - the correct question id and answer - for a random
|
||||
* question.
|
||||
@@ -2702,11 +2804,12 @@ function get_filesdir_from_context($context){
|
||||
* @return mixed return integer real question id or false if there was an
|
||||
* error..
|
||||
*/
|
||||
-function question_get_real_state($state){
|
||||
+function question_get_real_state($state) {
|
||||
+ global $OUTPUT;
|
||||
$realstate = clone($state);
|
||||
$matches = array();
|
||||
if (!preg_match('|^random([0-9]+)-(.*)|', $state->answer, $matches)){
|
||||
- notify(get_string('errorrandom', 'quiz_statistics'));
|
||||
+ echo $OUTPUT->notification(get_string('errorrandom', 'quiz_statistics'));
|
||||
return false;
|
||||
} else {
|
||||
$realstate->question = $matches[1];
|
||||
@@ -2770,4 +2877,389 @@ function question_get_toggleflag_checksum($attemptid, $questionid, $sessionid, $
|
||||
return md5($attemptid . "_" . $user->secret . "_" . $questionid . "_" . $sessionid);
|
||||
}
|
||||
|
||||
-?>
|
||||
+/**
|
||||
+ * Adds question bank setting links to the given navigation node if caps are met.
|
||||
+ *
|
||||
+ * @param navigation_node $navigationnode The navigation node to add the question branch to
|
||||
+ * @param stdClass $context
|
||||
+ * @return navigation_node Returns the question branch that was added
|
||||
+ */
|
||||
+function question_extend_settings_navigation(navigation_node $navigationnode, $context) {
|
||||
+ global $PAGE;
|
||||
+
|
||||
+ if ($context->contextlevel == CONTEXT_COURSE) {
|
||||
+ $params = array('courseid'=>$context->instanceid);
|
||||
+ } else if ($context->contextlevel == CONTEXT_MODULE) {
|
||||
+ $params = array('cmid'=>$context->instanceid);
|
||||
+ } else {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ $questionnode = $navigationnode->add(get_string('questionbank','question'), new moodle_url('/question/edit.php', $params), navigation_node::TYPE_CONTAINER);
|
||||
+
|
||||
+ $contexts = new question_edit_contexts($context);
|
||||
+ if ($contexts->have_one_edit_tab_cap('questions')) {
|
||||
+ $questionnode->add(get_string('questions', 'quiz'), new moodle_url('/question/edit.php', $params), navigation_node::TYPE_SETTING);
|
||||
+ }
|
||||
+ if ($contexts->have_one_edit_tab_cap('categories')) {
|
||||
+ $questionnode->add(get_string('categories', 'quiz'), new moodle_url('/question/category.php', $params), navigation_node::TYPE_SETTING);
|
||||
+ }
|
||||
+ if ($contexts->have_one_edit_tab_cap('import')) {
|
||||
+ $questionnode->add(get_string('import', 'quiz'), new moodle_url('/question/import.php', $params), navigation_node::TYPE_SETTING);
|
||||
+ }
|
||||
+ if ($contexts->have_one_edit_tab_cap('export')) {
|
||||
+ $questionnode->add(get_string('export', 'quiz'), new moodle_url('/question/export.php', $params), navigation_node::TYPE_SETTING);
|
||||
+ }
|
||||
+
|
||||
+ return $questionnode;
|
||||
+}
|
||||
+
|
||||
+class question_edit_contexts {
|
||||
+
|
||||
+ public static $CAPS = array(
|
||||
+ 'editq' => array('moodle/question:add',
|
||||
+ 'moodle/question:editmine',
|
||||
+ 'moodle/question:editall',
|
||||
+ 'moodle/question:viewmine',
|
||||
+ 'moodle/question:viewall',
|
||||
+ 'moodle/question:usemine',
|
||||
+ 'moodle/question:useall',
|
||||
+ 'moodle/question:movemine',
|
||||
+ 'moodle/question:moveall'),
|
||||
+ 'questions'=>array('moodle/question:add',
|
||||
+ 'moodle/question:editmine',
|
||||
+ 'moodle/question:editall',
|
||||
+ 'moodle/question:viewmine',
|
||||
+ 'moodle/question:viewall',
|
||||
+ 'moodle/question:movemine',
|
||||
+ 'moodle/question:moveall'),
|
||||
+ 'categories'=>array('moodle/question:managecategory'),
|
||||
+ 'import'=>array('moodle/question:add'),
|
||||
+ 'export'=>array('moodle/question:viewall', 'moodle/question:viewmine'));
|
||||
+
|
||||
+ protected $allcontexts;
|
||||
+
|
||||
+ /**
|
||||
+ * @param current context
|
||||
+ */
|
||||
+ public function question_edit_contexts($thiscontext){
|
||||
+ $pcontextids = get_parent_contexts($thiscontext);
|
||||
+ $contexts = array($thiscontext);
|
||||
+ foreach ($pcontextids as $pcontextid){
|
||||
+ $contexts[] = get_context_instance_by_id($pcontextid);
|
||||
+ }
|
||||
+ $this->allcontexts = $contexts;
|
||||
+ }
|
||||
+ /**
|
||||
+ * @return array all parent contexts
|
||||
+ */
|
||||
+ public function all(){
|
||||
+ return $this->allcontexts;
|
||||
+ }
|
||||
+ /**
|
||||
+ * @return object lowest context which must be either the module or course context
|
||||
+ */
|
||||
+ public function lowest(){
|
||||
+ return $this->allcontexts[0];
|
||||
+ }
|
||||
+ /**
|
||||
+ * @param string $cap capability
|
||||
+ * @return array parent contexts having capability, zero based index
|
||||
+ */
|
||||
+ public function having_cap($cap){
|
||||
+ $contextswithcap = array();
|
||||
+ foreach ($this->allcontexts as $context){
|
||||
+ if (has_capability($cap, $context)){
|
||||
+ $contextswithcap[] = $context;
|
||||
+ }
|
||||
+ }
|
||||
+ return $contextswithcap;
|
||||
+ }
|
||||
+ /**
|
||||
+ * @param array $caps capabilities
|
||||
+ * @return array parent contexts having at least one of $caps, zero based index
|
||||
+ */
|
||||
+ public function having_one_cap($caps){
|
||||
+ $contextswithacap = array();
|
||||
+ foreach ($this->allcontexts as $context){
|
||||
+ foreach ($caps as $cap){
|
||||
+ if (has_capability($cap, $context)){
|
||||
+ $contextswithacap[] = $context;
|
||||
+ break; //done with caps loop
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return $contextswithacap;
|
||||
+ }
|
||||
+ /**
|
||||
+ * @param string $tabname edit tab name
|
||||
+ * @return array parent contexts having at least one of $caps, zero based index
|
||||
+ */
|
||||
+ public function having_one_edit_tab_cap($tabname){
|
||||
+ return $this->having_one_cap(self::$CAPS[$tabname]);
|
||||
+ }
|
||||
+ /**
|
||||
+ * Has at least one parent context got the cap $cap?
|
||||
+ *
|
||||
+ * @param string $cap capability
|
||||
+ * @return boolean
|
||||
+ */
|
||||
+ public function have_cap($cap){
|
||||
+ return (count($this->having_cap($cap)));
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Has at least one parent context got one of the caps $caps?
|
||||
+ *
|
||||
+ * @param array $caps capability
|
||||
+ * @return boolean
|
||||
+ */
|
||||
+ public function have_one_cap($caps){
|
||||
+ foreach ($caps as $cap) {
|
||||
+ if ($this->have_cap($cap)) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+ /**
|
||||
+ * Has at least one parent context got one of the caps for actions on $tabname
|
||||
+ *
|
||||
+ * @param string $tabname edit tab name
|
||||
+ * @return boolean
|
||||
+ */
|
||||
+ public function have_one_edit_tab_cap($tabname){
|
||||
+ return $this->have_one_cap(self::$CAPS[$tabname]);
|
||||
+ }
|
||||
+ /**
|
||||
+ * Throw error if at least one parent context hasn't got the cap $cap
|
||||
+ *
|
||||
+ * @param string $cap capability
|
||||
+ */
|
||||
+ public function require_cap($cap){
|
||||
+ if (!$this->have_cap($cap)){
|
||||
+ print_error('nopermissions', '', '', $cap);
|
||||
+ }
|
||||
+ }
|
||||
+ /**
|
||||
+ * Throw error if at least one parent context hasn't got one of the caps $caps
|
||||
+ *
|
||||
+ * @param array $cap capabilities
|
||||
+ */
|
||||
+ public function require_one_cap($caps) {
|
||||
+ if (!$this->have_one_cap($caps)) {
|
||||
+ $capsstring = join($caps, ', ');
|
||||
+ print_error('nopermissions', '', '', $capsstring);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Throw error if at least one parent context hasn't got one of the caps $caps
|
||||
+ *
|
||||
+ * @param string $tabname edit tab name
|
||||
+ */
|
||||
+ public function require_one_edit_tab_cap($tabname){
|
||||
+ if (!$this->have_one_edit_tab_cap($tabname)) {
|
||||
+ print_error('nopermissions', '', '', 'access question edit tab '.$tabname);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Rewrite question url, file_rewrite_pluginfile_urls always build url by
|
||||
+ * $file/$contextid/$component/$filearea/$itemid/$pathname_in_text, so we cannot add
|
||||
+ * extra questionid and attempted in url by it, so we create quiz_rewrite_question_urls
|
||||
+ * to build url here
|
||||
+ *
|
||||
+ * @param string $text text being processed
|
||||
+ * @param string $file the php script used to serve files
|
||||
+ * @param int $contextid
|
||||
+ * @param string $component component
|
||||
+ * @param string $filearea filearea
|
||||
+ * @param array $ids other IDs will be used to check file permission
|
||||
+ * @param int $itemid
|
||||
+ * @param array $options
|
||||
+ * @return string
|
||||
+ */
|
||||
+function quiz_rewrite_question_urls($text, $file, $contextid, $component, $filearea, array $ids, $itemid, array $options=null) {
|
||||
+ global $CFG;
|
||||
+
|
||||
+ $options = (array)$options;
|
||||
+ if (!isset($options['forcehttps'])) {
|
||||
+ $options['forcehttps'] = false;
|
||||
+ }
|
||||
+
|
||||
+ if (!$CFG->slasharguments) {
|
||||
+ $file = $file . '?file=';
|
||||
+ }
|
||||
+
|
||||
+ $baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
|
||||
+
|
||||
+ if (!empty($ids)) {
|
||||
+ $baseurl .= (implode('/', $ids) . '/');
|
||||
+ }
|
||||
+
|
||||
+ if ($itemid !== null) {
|
||||
+ $baseurl .= "$itemid/";
|
||||
+ }
|
||||
+
|
||||
+ if ($options['forcehttps']) {
|
||||
+ $baseurl = str_replace('http://', 'https://', $baseurl);
|
||||
+ }
|
||||
+
|
||||
+ return str_replace('@@PLUGINFILE@@/', $baseurl, $text);
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Called by pluginfile.php to serve files related to the 'question' core
|
||||
+ * component and for files belonging to qtypes.
|
||||
+ *
|
||||
+ * For files that relate to questions in a question_attempt, then we delegate to
|
||||
+ * a function in the component that owns the attempt (for example in the quiz,
|
||||
+ * or in core question preview) to get necessary inforation.
|
||||
+ *
|
||||
+ * (Note that, at the moment, all question file areas relate to questions in
|
||||
+ * attempts, so the If at the start of the last paragraph is always true.)
|
||||
+ *
|
||||
+ * Does not return, either calls send_file_not_found(); or serves the file.
|
||||
+ *
|
||||
+ * @param object $course course settings object
|
||||
+ * @param object $context context object
|
||||
+ * @param string $component the name of the component we are serving files for.
|
||||
+ * @param string $filearea the name of the file area.
|
||||
+ * @param array $args the remaining bits of the file path.
|
||||
+ * @param bool $forcedownload whether the user must be forced to download the file.
|
||||
+ */
|
||||
+function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) {
|
||||
+ global $DB, $CFG;
|
||||
+
|
||||
+ list($context, $course, $cm) = get_context_info_array($context->id);
|
||||
+ require_login($course, false, $cm);
|
||||
+
|
||||
+ if ($filearea === 'export') {
|
||||
+ require_once($CFG->dirroot . '/question/editlib.php');
|
||||
+ $contexts = new question_edit_contexts($context);
|
||||
+ // check export capability
|
||||
+ $contexts->require_one_edit_tab_cap('export');
|
||||
+ $category_id = (int)array_shift($args);
|
||||
+ $format = array_shift($args);
|
||||
+ $cattofile = array_shift($args);
|
||||
+ $contexttofile = array_shift($args);
|
||||
+ $filename = array_shift($args);
|
||||
+
|
||||
+ // load parent class for import/export
|
||||
+ require_once($CFG->dirroot . '/question/format.php');
|
||||
+ require_once($CFG->dirroot . '/question/editlib.php');
|
||||
+ require_once($CFG->dirroot . '/question/format/' . $format . '/format.php');
|
||||
+
|
||||
+ $classname = 'qformat_' . $format;
|
||||
+ if (!class_exists($classname)) {
|
||||
+ send_file_not_found();
|
||||
+ }
|
||||
+
|
||||
+ $qformat = new $classname();
|
||||
+
|
||||
+ if (!$category = $DB->get_record('question_categories', array('id' => $category_id))) {
|
||||
+ send_file_not_found();
|
||||
+ }
|
||||
+
|
||||
+ $qformat->setCategory($category);
|
||||
+ $qformat->setContexts($contexts->having_one_edit_tab_cap('export'));
|
||||
+ $qformat->setCourse($course);
|
||||
+
|
||||
+ if ($cattofile == 'withcategories') {
|
||||
+ $qformat->setCattofile(true);
|
||||
+ } else {
|
||||
+ $qformat->setCattofile(false);
|
||||
+ }
|
||||
+
|
||||
+ if ($contexttofile == 'withcontexts') {
|
||||
+ $qformat->setContexttofile(true);
|
||||
+ } else {
|
||||
+ $qformat->setContexttofile(false);
|
||||
+ }
|
||||
+
|
||||
+ if (!$qformat->exportpreprocess()) {
|
||||
+ send_file_not_found();
|
||||
+ print_error('exporterror', 'question', $thispageurl->out());
|
||||
+ }
|
||||
+
|
||||
+ // export data to moodle file pool
|
||||
+ if (!$content = $qformat->exportprocess(true)) {
|
||||
+ send_file_not_found();
|
||||
+ }
|
||||
+
|
||||
+ //DEBUG
|
||||
+ //echo '<textarea cols=90 rows=20>';
|
||||
+ //echo $content;
|
||||
+ //echo '</textarea>';
|
||||
+ //die;
|
||||
+ send_file($content, $filename, 0, 0, true, true, $qformat->mime_type());
|
||||
+ }
|
||||
+
|
||||
+ $attemptid = (int)array_shift($args);
|
||||
+ $questionid = (int)array_shift($args);
|
||||
+
|
||||
+
|
||||
+ if ($attemptid === 0) {
|
||||
+ // preview
|
||||
+ require_once($CFG->dirroot . '/question/previewlib.php');
|
||||
+ return question_preview_question_pluginfile($course, $context,
|
||||
+ $component, $filearea, $attemptid, $questionid, $args, $forcedownload);
|
||||
+
|
||||
+ } else {
|
||||
+ $module = $DB->get_field('question_attempts', 'modulename',
|
||||
+ array('id' => $attemptid));
|
||||
+
|
||||
+ $dir = get_component_directory($module);
|
||||
+ if (!file_exists("$dir/lib.php")) {
|
||||
+ send_file_not_found();
|
||||
+ }
|
||||
+ include_once("$dir/lib.php");
|
||||
+
|
||||
+ $filefunction = $module . '_question_pluginfile';
|
||||
+ if (!function_exists($filefunction)) {
|
||||
+ send_file_not_found();
|
||||
+ }
|
||||
+
|
||||
+ $filefunction($course, $context, $component, $filearea, $attemptid, $questionid,
|
||||
+ $args, $forcedownload);
|
||||
+
|
||||
+ send_file_not_found();
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Final test for whether a studnet should be allowed to see a particular file.
|
||||
+ * This delegates the decision to the question type plugin.
|
||||
+ *
|
||||
+ * @param object $question The question to be rendered.
|
||||
+ * @param object $state The state to render the question in.
|
||||
+ * @param object $options An object specifying the rendering options.
|
||||
+ * @param string $component the name of the component we are serving files for.
|
||||
+ * @param string $filearea the name of the file area.
|
||||
+ * @param array $args the remaining bits of the file path.
|
||||
+ * @param bool $forcedownload whether the user must be forced to download the file.
|
||||
+ */
|
||||
+function question_check_file_access($question, $state, $options, $contextid, $component,
|
||||
+ $filearea, $args, $forcedownload) {
|
||||
+ global $QTYPES;
|
||||
+ return $QTYPES[$question->qtype]->check_file_access($question, $state, $options, $contextid, $component,
|
||||
+ $filearea, $args, $forcedownload);
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Create url for question export
|
||||
+ *
|
||||
+ * @param int $contextid, current context
|
||||
+ * @param int $categoryid, categoryid
|
||||
+ * @param string $format
|
||||
+ * @param string $withcategories
|
||||
+ * @param string $ithcontexts
|
||||
+ * @param moodle_url export file url
|
||||
+ */
|
||||
+function question_make_export_url($contextid, $categoryid, $format, $withcategories, $withcontexts, $filename) {
|
||||
+ global $CFG;
|
||||
+ $urlbase = "$CFG->httpswwwroot/pluginfile.php";
|
||||
+ return moodle_url::make_file_url($urlbase, "/$contextid/question/export/{$categoryid}/{$format}/{$withcategories}/{$withcontexts}/{$filename}", true);
|
||||
+}
|
1720
question/todo/questionlib_qe.diff.txt
Normal file
1720
question/todo/questionlib_qe.diff.txt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -33,6 +33,10 @@ require_once($CFG->libdir . '/questionlib.php');
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qtype_description extends question_type {
|
||||
function is_real_question_type() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function is_usable_by_random() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ class default_questiontype {
|
|||
/**
|
||||
* Return an instance of the question editing form definition. This looks for a
|
||||
* class called edit_{$this->name()}_question_form in the file
|
||||
* {$CFG->dirroot}/question/type/{$this->name()}/edit_{$this->name()}_question_form.php
|
||||
* question/type/{$this->name()}/edit_{$this->name()}_question_form.php
|
||||
* and if it exists returns an instance of it.
|
||||
*
|
||||
* @param string $submiturl passed on to the constructor call.
|
||||
|
@ -880,606 +880,6 @@ class default_questiontype {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the question including the number, grading details, content,
|
||||
* feedback and interactions
|
||||
*
|
||||
* This function prints the question including the question number,
|
||||
* grading details, content for the question, any feedback for the previously
|
||||
* submitted responses and the interactions. The default implementation calls
|
||||
* various other methods to print each of these parts and most question types
|
||||
* will just override those methods.
|
||||
* @param object $question The question to be rendered. Question type
|
||||
* specific information is included. The
|
||||
* maximum possible grade is in ->maxgrade. The name
|
||||
* prefix for any named elements is in ->name_prefix.
|
||||
* @param object $state The state to render the question in. The grading
|
||||
* information is in ->grade, ->raw_grade and
|
||||
* ->penalty. The current responses are in
|
||||
* ->responses. This is an associative array (or the
|
||||
* empty string or null in the case of no responses
|
||||
* submitted). The last graded state is in
|
||||
* ->last_graded (hence the most recently graded
|
||||
* responses are in ->last_graded->responses). The
|
||||
* question type specific information is also
|
||||
* included.
|
||||
* @param integer $number The number for this question.
|
||||
* @param object $cmoptions
|
||||
* @param object $options An object describing the rendering options.
|
||||
*/
|
||||
function print_question(&$question, &$state, $number, $cmoptions, $options, $context=null) {
|
||||
/* The default implementation should work for most question types
|
||||
provided the member functions it calls are overridden where required.
|
||||
The layout is determined by the template question.html */
|
||||
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$context = $this->get_context_by_category_id($question->category);
|
||||
$question->questiontext = quiz_rewrite_question_urls($question->questiontext, 'pluginfile.php', $context->id, 'question', 'questiontext', array($state->attempt, $state->question), $question->id);
|
||||
|
||||
$question->generalfeedback = quiz_rewrite_question_urls($question->generalfeedback, 'pluginfile.php', $context->id, 'question', 'generalfeedback', array($state->attempt, $state->question), $question->id);
|
||||
|
||||
$isgraded = question_state_is_graded($state->last_graded);
|
||||
|
||||
if (isset($question->randomquestionid)) {
|
||||
$actualquestionid = $question->randomquestionid;
|
||||
} else {
|
||||
$actualquestionid = $question->id;
|
||||
}
|
||||
|
||||
// For editing teachers print a link to an editing popup window
|
||||
$editlink = $this->get_question_edit_link($question, $cmoptions, $options);
|
||||
|
||||
$generalfeedback = '';
|
||||
if ($isgraded && $options->generalfeedback) {
|
||||
$generalfeedback = $this->format_text($question->generalfeedback,
|
||||
$question->generalfeedbackformat, $cmoptions);
|
||||
}
|
||||
|
||||
$grade = '';
|
||||
if ($question->maxgrade > 0 && $options->scores) {
|
||||
if ($cmoptions->optionflags & QUESTION_ADAPTIVE) {
|
||||
if ($isgraded) {
|
||||
$grade = question_format_grade($cmoptions, $state->last_graded->grade).'/';
|
||||
} else {
|
||||
$grade = '--/';
|
||||
}
|
||||
}
|
||||
$grade .= question_format_grade($cmoptions, $question->maxgrade);
|
||||
}
|
||||
|
||||
$formatoptions = new stdClass;
|
||||
$formatoptions->para = false;
|
||||
$comment = format_text($state->manualcomment, $state->manualcommentformat,
|
||||
$formatoptions, $cmoptions->course);
|
||||
$commentlink = '';
|
||||
|
||||
if (!empty($options->questioncommentlink)) {
|
||||
$strcomment = get_string('commentorgrade', 'quiz');
|
||||
|
||||
$link = new moodle_url("$options->questioncommentlink?attempt=$state->attempt&question=$actualquestionid");
|
||||
$action = new popup_action('click', $link, 'commentquestion', array('height' => 480, 'width' => 750));
|
||||
$commentlink = $OUTPUT->container($OUTPUT->action_link($link, $strcomment, $action), 'commentlink');
|
||||
}
|
||||
|
||||
$history = $this->history($question, $state, $number, $cmoptions, $options);
|
||||
|
||||
include "$CFG->dirroot/question/type/question.html";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the question flag, assuming $flagsoption allows it. You will probably
|
||||
* never need to override this method.
|
||||
*
|
||||
* @param object $question the question
|
||||
* @param object $state its current state
|
||||
* @param integer $flagsoption the option that says whether flags should be displayed.
|
||||
*/
|
||||
protected function print_question_flag($question, $state, $flagsoption) {
|
||||
global $CFG, $PAGE;
|
||||
switch ($flagsoption) {
|
||||
case QUESTION_FLAGSSHOWN:
|
||||
$flagcontent = $this->get_question_flag_tag($state->flagged);
|
||||
break;
|
||||
case QUESTION_FLAGSEDITABLE:
|
||||
$id = $question->name_prefix . '_flagged';
|
||||
if ($state->flagged) {
|
||||
$checked = 'checked="checked" ';
|
||||
} else {
|
||||
$checked = '';
|
||||
}
|
||||
$qsid = $state->questionsessionid;
|
||||
$aid = $state->attempt;
|
||||
$qid = $state->question;
|
||||
$checksum = question_get_toggleflag_checksum($aid, $qid, $qsid);
|
||||
$postdata = "qsid=$qsid&aid=$aid&qid=$qid&checksum=$checksum&sesskey=" .
|
||||
sesskey() . '&newstate=';
|
||||
$flagcontent = '<input type="checkbox" id="' . $id . '" name="' . $id .
|
||||
'" class="questionflagcheckbox" value="1" ' . $checked . ' />' .
|
||||
'<input type="hidden" value="' . s($postdata) . '" class="questionflagpostdata" />' .
|
||||
'<label id="' . $id . 'label" for="' . $id .
|
||||
'" class="questionflaglabel">' . $this->get_question_flag_tag(
|
||||
$state->flagged, $id . 'img') . '</label>' . "\n";
|
||||
question_init_qengine_js();
|
||||
break;
|
||||
default:
|
||||
$flagcontent = '';
|
||||
}
|
||||
if ($flagcontent) {
|
||||
echo '<div class="questionflag">' . $flagcontent . "</div>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out the actual img tag needed for the flag
|
||||
*
|
||||
* @param boolean $flagged whether the question is currently flagged.
|
||||
* @param string $id an id to be added as an attribute to the img (optional).
|
||||
* @return string the img tag.
|
||||
*/
|
||||
protected function get_question_flag_tag($flagged, $id = '') {
|
||||
global $OUTPUT;
|
||||
if ($id) {
|
||||
$id = 'id="' . $id . '" ';
|
||||
}
|
||||
if ($flagged) {
|
||||
$img = 'i/flagged';
|
||||
} else {
|
||||
$img = 'i/unflagged';
|
||||
}
|
||||
return '<img ' . $id . 'src="' . $OUTPUT->pix_url($img) .
|
||||
'" alt="' . get_string('flagthisquestion', 'question') . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a link to an edit icon for this question, if the current user is allowed
|
||||
* to edit it.
|
||||
*
|
||||
* @param object $question the question object.
|
||||
* @param object $cmoptions the options from the module. If $cmoptions->thispageurl is set
|
||||
* then the link will be to edit the question in this browser window, then return to
|
||||
* $cmoptions->thispageurl. Otherwise the link will be to edit in a popup.
|
||||
* @return string the HTML of the link, or nothing it the currenty user is not allowed to edit.
|
||||
*/
|
||||
function get_question_edit_link($question, $cmoptions, $options) {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
/// Is this user allowed to edit this question?
|
||||
if (!empty($options->noeditlink) || !question_has_capability_on($question, 'edit')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/// Work out the right URL.
|
||||
$url = new moodle_url('/question/question.php', array('id' => $question->id));
|
||||
if (!empty($cmoptions->cmid)) {
|
||||
$url->param('cmid', $cmoptions->cmid);
|
||||
} else if (!empty($cmoptions->course)) {
|
||||
$url->param('courseid', $cmoptions->course);
|
||||
} else {
|
||||
print_error('missingcourseorcmidtolink', 'question');
|
||||
}
|
||||
|
||||
$icon = new pix_icon('t/edit', get_string('edit'));
|
||||
|
||||
$action = null;
|
||||
if (!empty($cmoptions->thispageurl)) {
|
||||
// The module allow editing in the same window, print an ordinary
|
||||
// link with a returnurl.
|
||||
$url->param('returnurl', $cmoptions->thispageurl);
|
||||
} else {
|
||||
// We have to edit in a pop-up.
|
||||
$url->param('inpopup', 1);
|
||||
$action = new popup_action('click', $link, 'editquestion');
|
||||
}
|
||||
|
||||
return $OUTPUT->action_icon($url, $icon, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print history of responses
|
||||
*
|
||||
* Used by print_question()
|
||||
*/
|
||||
function history($question, $state, $number, $cmoptions, $options) {
|
||||
global $DB, $OUTPUT;
|
||||
|
||||
if (empty($options->history)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$params = array('aid' => $state->attempt);
|
||||
if (isset($question->randomquestionid)) {
|
||||
$params['qid'] = $question->randomquestionid;
|
||||
$randomprefix = 'random' . $question->id . '-';
|
||||
} else {
|
||||
$params['qid'] = $question->id;
|
||||
$randomprefix = '';
|
||||
}
|
||||
if ($options->history == 'all') {
|
||||
$eventtest = 'event > 0';
|
||||
} else {
|
||||
$eventtest = 'event IN (' . QUESTION_EVENTS_GRADED . ')';
|
||||
}
|
||||
$states = $DB->get_records_select('question_states',
|
||||
'attempt = :aid AND question = :qid AND ' . $eventtest, $params, 'seq_number ASC');
|
||||
if (count($states) <= 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strreviewquestion = get_string('reviewresponse', 'quiz');
|
||||
$table = new html_table();
|
||||
$table->width = '100%';
|
||||
$table->head = array (
|
||||
get_string('numberabbr', 'quiz'),
|
||||
get_string('action', 'quiz'),
|
||||
get_string('response', 'quiz'),
|
||||
get_string('time'),
|
||||
);
|
||||
if ($options->scores) {
|
||||
$table->head[] = get_string('score', 'quiz');
|
||||
$table->head[] = get_string('grade', 'quiz');
|
||||
}
|
||||
|
||||
foreach ($states as $st) {
|
||||
if ($randomprefix && strpos($st->answer, $randomprefix) === 0) {
|
||||
$st->answer = substr($st->answer, strlen($randomprefix));
|
||||
}
|
||||
$st->responses[''] = $st->answer;
|
||||
$this->restore_session_and_responses($question, $st);
|
||||
|
||||
if ($state->id == $st->id) {
|
||||
$link = '<b>' . $st->seq_number . '</b>';
|
||||
} else if (isset($options->questionreviewlink)) {
|
||||
$reviewlink = new moodle_url($options->questionreviewlink);
|
||||
$reviewlink->params(array('state'=>$st->id,'question'=>$question->id));
|
||||
$link = new moodle_url($reviewlink);
|
||||
$action = new popup_action('click', $link, 'reviewquestion', array('height' => 450, 'width' => 650));
|
||||
$link = $OUTPUT->action_link($link, $st->seq_number, $action, array('title'=>$strreviewquestion));
|
||||
} else {
|
||||
$link = $st->seq_number;
|
||||
}
|
||||
|
||||
if ($state->id == $st->id) {
|
||||
$b = '<b>';
|
||||
$be = '</b>';
|
||||
} else {
|
||||
$b = '';
|
||||
$be = '';
|
||||
}
|
||||
|
||||
$data = array (
|
||||
$link,
|
||||
$b.get_string('event'.$st->event, 'quiz').$be,
|
||||
$b.$this->response_summary($question, $st).$be,
|
||||
$b.userdate($st->timestamp, get_string('timestr', 'quiz')).$be,
|
||||
);
|
||||
if ($options->scores) {
|
||||
$data[] = $b.question_format_grade($cmoptions, $st->raw_grade).$be;
|
||||
$data[] = $b.question_format_grade($cmoptions, $st->raw_grade).$be;
|
||||
}
|
||||
$table->data[] = $data;
|
||||
}
|
||||
return html_writer::table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the score obtained and maximum score available plus any penalty
|
||||
* information
|
||||
*
|
||||
* This function prints a summary of the scoring in the most recently
|
||||
* graded state (the question may not have been submitted for marking at
|
||||
* the current state). The default implementation should be suitable for most
|
||||
* question types.
|
||||
* @param object $question The question for which the grading details are
|
||||
* to be rendered. Question type specific information
|
||||
* is included. The maximum possible grade is in
|
||||
* ->maxgrade.
|
||||
* @param object $state The state. In particular the grading information
|
||||
* is in ->grade, ->raw_grade and ->penalty.
|
||||
* @param object $cmoptions
|
||||
* @param object $options An object describing the rendering options.
|
||||
*/
|
||||
function print_question_grading_details(&$question, &$state, $cmoptions, $options) {
|
||||
/* The default implementation prints the number of marks if no attempt
|
||||
has been made. Otherwise it displays the grade obtained out of the
|
||||
maximum grade available and a warning if a penalty was applied for the
|
||||
attempt and displays the overall grade obtained counting all previous
|
||||
responses (and penalties) */
|
||||
|
||||
if (QUESTION_EVENTDUPLICATE == $state->event) {
|
||||
echo ' ';
|
||||
print_string('duplicateresponse', 'quiz');
|
||||
}
|
||||
if ($question->maxgrade > 0 && $options->scores) {
|
||||
if (question_state_is_graded($state->last_graded)) {
|
||||
// Display the grading details from the last graded state
|
||||
$grade = new stdClass;
|
||||
$grade->cur = question_format_grade($cmoptions, $state->last_graded->grade);
|
||||
$grade->max = question_format_grade($cmoptions, $question->maxgrade);
|
||||
$grade->raw = question_format_grade($cmoptions, $state->last_graded->raw_grade);
|
||||
|
||||
// let student know wether the answer was correct
|
||||
$class = question_get_feedback_class($state->last_graded->raw_grade /
|
||||
$question->maxgrade);
|
||||
echo '<div class="correctness ' . $class . '">' . get_string($class, 'quiz') . '</div>';
|
||||
|
||||
echo '<div class="gradingdetails">';
|
||||
// print grade for this submission
|
||||
print_string('gradingdetails', 'quiz', $grade);
|
||||
if ($cmoptions->penaltyscheme) {
|
||||
// print details of grade adjustment due to penalties
|
||||
if ($state->last_graded->raw_grade > $state->last_graded->grade){
|
||||
echo ' ';
|
||||
print_string('gradingdetailsadjustment', 'quiz', $grade);
|
||||
}
|
||||
// print info about new penalty
|
||||
// penalty is relevant only if the answer is not correct and further attempts are possible
|
||||
if (($state->last_graded->raw_grade < $question->maxgrade / 1.01)
|
||||
and (QUESTION_EVENTCLOSEANDGRADE != $state->event)) {
|
||||
|
||||
if ('' !== $state->last_graded->penalty && ((float)$state->last_graded->penalty) > 0.0) {
|
||||
// A penalty was applied so display it
|
||||
echo ' ';
|
||||
print_string('gradingdetailspenalty', 'quiz', question_format_grade($cmoptions, $state->last_graded->penalty));
|
||||
} else {
|
||||
/* No penalty was applied even though the answer was
|
||||
not correct (eg. a syntax error) so tell the student
|
||||
that they were not penalised for the attempt */
|
||||
echo ' ';
|
||||
print_string('gradingdetailszeropenalty', 'quiz');
|
||||
}
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the main content of the question including any interactions
|
||||
*
|
||||
* This function prints the main content of the question including the
|
||||
* interactions for the question in the state given. The last graded responses
|
||||
* are printed or indicated and the current responses are selected or filled in.
|
||||
* Any names (eg. for any form elements) are prefixed with $question->name_prefix.
|
||||
* This method is called from the print_question method.
|
||||
* @param object $question The question to be rendered. Question type
|
||||
* specific information is included. The name
|
||||
* prefix for any named elements is in ->name_prefix.
|
||||
* @param object $state The state to render the question in. The grading
|
||||
* information is in ->grade, ->raw_grade and
|
||||
* ->penalty. The current responses are in
|
||||
* ->responses. This is an associative array (or the
|
||||
* empty string or null in the case of no responses
|
||||
* submitted). The last graded state is in
|
||||
* ->last_graded (hence the most recently graded
|
||||
* responses are in ->last_graded->responses). The
|
||||
* question type specific information is also
|
||||
* included.
|
||||
* The state is passed by reference because some adaptive
|
||||
* questions may want to update it during rendering
|
||||
* @param object $cmoptions
|
||||
* @param object $options An object describing the rendering options.
|
||||
*/
|
||||
function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
|
||||
/* This default implementation prints an error and must be overridden
|
||||
by all question type implementations, unless the default implementation
|
||||
of print_question has been overridden. */
|
||||
global $OUTPUT;
|
||||
echo $OUTPUT->notification('Error: Question formulation and input controls has not'
|
||||
.' been implemented for question type '.$this->name());
|
||||
}
|
||||
|
||||
function check_file_access($question, $state, $options, $contextid, $component,
|
||||
$filearea, $args) {
|
||||
|
||||
if ($component == 'question' && $filearea == 'questiontext') {
|
||||
// Question text always visible.
|
||||
return true;
|
||||
|
||||
} else if ($component == 'question' && $filearea = 'generalfeedback') {
|
||||
return $options->generalfeedback && question_state_is_graded($state->last_graded);
|
||||
|
||||
} else {
|
||||
// Unrecognised component or filearea.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the submit button(s) for the question in the given state
|
||||
*
|
||||
* This function prints the submit button(s) for the question in the
|
||||
* given state. The name of any button created will be prefixed with the
|
||||
* unique prefix for the question in $question->name_prefix. The suffix
|
||||
* 'submit' is reserved for the single question submit button and the suffix
|
||||
* 'validate' is reserved for the single question validate button (for
|
||||
* question types which support it). Other suffixes will result in a response
|
||||
* of that name in $state->responses which the printing and grading methods
|
||||
* can then use.
|
||||
* @param object $question The question for which the submit button(s) are to
|
||||
* be rendered. Question type specific information is
|
||||
* included. The name prefix for any
|
||||
* named elements is in ->name_prefix.
|
||||
* @param object $state The state to render the buttons for. The
|
||||
* question type specific information is also
|
||||
* included.
|
||||
* @param object $cmoptions
|
||||
* @param object $options An object describing the rendering options.
|
||||
*/
|
||||
function print_question_submit_buttons(&$question, &$state, $cmoptions, $options) {
|
||||
// The default implementation should be suitable for most question types.
|
||||
// It prints a mark button in the case where individual marking is allowed.
|
||||
if (($cmoptions->optionflags & QUESTION_ADAPTIVE) and !$options->readonly) {
|
||||
echo '<input type="submit" name="', $question->name_prefix, 'submit" value="',
|
||||
get_string('mark', 'quiz'), '" class="submit btn" />';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a summary of the student response
|
||||
*
|
||||
* This function returns a short string of no more than a given length that
|
||||
* summarizes the student's response in the given $state. This is used for
|
||||
* example in the response history table. This string should already be
|
||||
* formatted for output.
|
||||
* @return string The summary of the student response
|
||||
* @param object $question
|
||||
* @param object $state The state whose responses are to be summarized
|
||||
* @param int $length The maximum length of the returned string
|
||||
*/
|
||||
function response_summary($question, $state, $length = 80, $formatting = true) {
|
||||
// This should almost certainly be overridden
|
||||
$responses = $this->get_actual_response($question, $state);
|
||||
if ($formatting){
|
||||
$responses = $this->format_responses($responses, $question->questiontextformat);
|
||||
}
|
||||
$responses = implode('; ', $responses);
|
||||
return shorten_text($responses, $length);
|
||||
}
|
||||
/**
|
||||
* @param array responses is an array of responses.
|
||||
* @return formatted responses
|
||||
*/
|
||||
function format_responses($responses, $format){
|
||||
$toreturn = array();
|
||||
foreach ($responses as $response){
|
||||
$toreturn[] = $this->format_response($response, $format);
|
||||
}
|
||||
return $toreturn;
|
||||
}
|
||||
/**
|
||||
* @param string response is a response.
|
||||
* @return formatted response
|
||||
*/
|
||||
function format_response($response, $format){
|
||||
return s($response);
|
||||
}
|
||||
/**
|
||||
* Renders the question for printing and returns the LaTeX source produced
|
||||
*
|
||||
* This function should render the question suitable for a printed problem
|
||||
* or solution sheet in LaTeX and return the rendered output.
|
||||
* @return string The LaTeX output.
|
||||
* @param object $question The question to be rendered. Question type
|
||||
* specific information is included.
|
||||
* @param object $state The state to render the question in. The
|
||||
* question type specific information is also
|
||||
* included.
|
||||
* @param object $cmoptions
|
||||
* @param string $type Indicates if the question or the solution is to be
|
||||
* rendered with the values 'question' and
|
||||
* 'solution'.
|
||||
*/
|
||||
function get_texsource(&$question, &$state, $cmoptions, $type) {
|
||||
// The default implementation simply returns a string stating that
|
||||
// the question is only available online.
|
||||
|
||||
return get_string('onlineonly', 'texsheet');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two question states for equivalence of the student's responses
|
||||
*
|
||||
* The responses for the two states must be examined to see if they represent
|
||||
* equivalent answers to the question by the student. This method will be
|
||||
* invoked for each of the previous states of the question before grading
|
||||
* occurs. If the student is found to have already attempted the question
|
||||
* with equivalent responses then the attempt at the question is ignored;
|
||||
* grading does not occur and the state does not change. Thus they are not
|
||||
* penalized for this case.
|
||||
* @return boolean
|
||||
* @param object $question The question for which the states are to be
|
||||
* compared. Question type specific information is
|
||||
* included.
|
||||
* @param object $state The state of the question. The responses are in
|
||||
* ->responses. This is the only field of $state
|
||||
* that it is safe to use.
|
||||
* @param object $teststate The state whose responses are to be
|
||||
* compared. The state will be of the same age or
|
||||
* older than $state. If possible, the method should
|
||||
* only use the field $teststate->responses, however
|
||||
* any field that is set up by restore_session_and_responses
|
||||
* can be used.
|
||||
*/
|
||||
function compare_responses(&$question, $state, $teststate) {
|
||||
// The default implementation performs a comparison of the response
|
||||
// arrays. The ordering of the arrays does not matter.
|
||||
// Question types may wish to override this (eg. to ignore trailing
|
||||
// white space or to make "7.0" and "7" compare equal).
|
||||
|
||||
// In php neither == nor === compare arrays the way you want. The following
|
||||
// ensures that the arrays have the same keys, with the same values.
|
||||
$result = false;
|
||||
$diff1 = array_diff_assoc($state->responses, $teststate->responses);
|
||||
if (empty($diff1)) {
|
||||
$diff2 = array_diff_assoc($teststate->responses, $state->responses);
|
||||
$result = empty($diff2);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a response matches a given answer
|
||||
*
|
||||
* This method only applies to questions that use teacher-defined answers
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
function test_response(&$question, &$state, $answer) {
|
||||
$response = isset($state->responses['']) ? $state->responses[''] : '';
|
||||
return ($response == $answer->answer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs response processing and grading
|
||||
*
|
||||
* This function performs response processing and grading and updates
|
||||
* the state accordingly.
|
||||
* @return boolean Indicates success or failure.
|
||||
* @param object $question The question to be graded. Question type
|
||||
* specific information is included.
|
||||
* @param object $state The state of the question to grade. The current
|
||||
* responses are in ->responses. The last graded state
|
||||
* is in ->last_graded (hence the most recently graded
|
||||
* responses are in ->last_graded->responses). The
|
||||
* question type specific information is also
|
||||
* included. The ->raw_grade and ->penalty fields
|
||||
* must be updated. The method is able to
|
||||
* close the question session (preventing any further
|
||||
* attempts at this question) by setting
|
||||
* $state->event to QUESTION_EVENTCLOSEANDGRADE
|
||||
* @param object $cmoptions
|
||||
*/
|
||||
function grade_responses(&$question, &$state, $cmoptions) {
|
||||
// The default implementation uses the test_response method to
|
||||
// compare what the student entered against each of the possible
|
||||
// answers stored in the question, and uses the grade from the
|
||||
// first one that matches. It also sets the marks and penalty.
|
||||
// This should be good enought for most simple question types.
|
||||
|
||||
$state->raw_grade = 0;
|
||||
foreach($question->options->answers as $answer) {
|
||||
if($this->test_response($question, $state, $answer)) {
|
||||
$state->raw_grade = $answer->fraction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we don't assign negative or too high marks.
|
||||
$state->raw_grade = min(max((float) $state->raw_grade,
|
||||
0.0), 1.0) * $question->maxgrade;
|
||||
|
||||
// Update the penalty.
|
||||
$state->penalty = $question->penalty * $question->maxgrade;
|
||||
|
||||
// mark the state as graded
|
||||
$state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the editing wizard is finished, false otherwise.
|
||||
*
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
<?php // $Id$
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* The default questiontype class.
|
||||
*
|
||||
|
@ -11,14 +27,10 @@
|
|||
* @subpackage questiontypes
|
||||
*/
|
||||
|
||||
|
||||
require_once($CFG->dirroot . '/question/engine/lib.php');
|
||||
|
||||
// DONOTCOMMIT
|
||||
class default_questiontype {
|
||||
function plugin_dir() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the base class for Moodle question types.
|
||||
*
|
||||
|
@ -36,6 +48,11 @@ class default_questiontype {
|
|||
* @subpackage questiontypes
|
||||
*/
|
||||
class question_type {
|
||||
protected $fileoptions = array(
|
||||
'subdirs' => false,
|
||||
'maxfiles' => -1,
|
||||
'maxbytes' => 0,
|
||||
);
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
@ -47,15 +64,54 @@ class question_type {
|
|||
return substr(get_class($this), 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the full frankenstyle name for this plugin.
|
||||
*/
|
||||
public function plugin_name() {
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the name of this question type in the user's language.
|
||||
* You should not need to override this method, the default behaviour should be fine.
|
||||
*/
|
||||
public function local_name() {
|
||||
return get_string($this->name(), $this->plugin_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* The name this question should appear as in the create new question
|
||||
* dropdown.
|
||||
* dropdown. Override this method to return false if you don't want your
|
||||
* question type to be createable, for example if it is an abstract base type,
|
||||
* otherwise, you should not need to override this method.
|
||||
*
|
||||
* @return mixed the desired string, or false to hide this question type in the menu.
|
||||
*/
|
||||
public function menu_name() {
|
||||
$name = $this->name();
|
||||
return get_string($name, 'qtype_' . $name);
|
||||
return $this->local_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of other question types that this one requires in order to
|
||||
* work. For example, the calculated question type is a subclass of the
|
||||
* numerical question type, which is a subclass of the shortanswer question
|
||||
* type; and the randomsamatch question type requires the shortanswer type
|
||||
* to be installed.
|
||||
*
|
||||
* @return array any other question types that this one relies on. An empty
|
||||
* array if none.
|
||||
*/
|
||||
public function requires_qtypes() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean override this to return false if this is not really a
|
||||
* question type, for example the description question type is not
|
||||
* really a question type.
|
||||
*/
|
||||
public function is_real_question_type() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,7 +195,7 @@ class question_type {
|
|||
/**
|
||||
* Return an instance of the question editing form definition. This looks for a
|
||||
* class called edit_{$this->name()}_question_form in the file
|
||||
* {$CFG->docroot}/question/type/{$this->name()}/edit_{$this->name()}_question_form.php
|
||||
* question/type/{$this->name()}/edit_{$this->name()}_question_form.php
|
||||
* and if it exists returns an instance of it.
|
||||
*
|
||||
* @param string $submiturl passed on to the constructor call.
|
||||
|
@ -184,9 +240,12 @@ class question_type {
|
|||
* @param object $question
|
||||
* @param string $wizardnow is '' for first page.
|
||||
*/
|
||||
public function display_question_editing_page(&$mform, $question, $wizardnow) {
|
||||
$name = $this->name();
|
||||
print_heading_with_help($this->get_heading(empty($question->id)), $name, 'qtype_' . $name);
|
||||
public function display_question_editing_page($mform, $question, $wizardnow) {
|
||||
global $OUTPUT;
|
||||
$heading = $this->get_heading(empty($question->id));
|
||||
|
||||
echo $OUTPUT->heading_with_help($heading, $this->name(), $this->plugin_name());
|
||||
|
||||
$permissionstrs = array();
|
||||
if (!empty($question->id)){
|
||||
if ($question->formoptions->canedit){
|
||||
|
@ -200,13 +259,13 @@ class question_type {
|
|||
}
|
||||
}
|
||||
if (!$question->formoptions->movecontext && count($permissionstrs)){
|
||||
print_heading(get_string('permissionto', 'question'), 'center', 3);
|
||||
echo $OUTPUT->heading(get_string('permissionto', 'question'), 3);
|
||||
$html = '<ul>';
|
||||
foreach ($permissionstrs as $permissionstr){
|
||||
$html .= '<li>'.$permissionstr.'</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
print_box($html, 'boxwidthnarrow boxaligncenter generalbox');
|
||||
echo $OUTPUT->box($html, 'boxwidthnarrow boxaligncenter generalbox');
|
||||
}
|
||||
$mform->display();
|
||||
}
|
||||
|
@ -217,13 +276,12 @@ class question_type {
|
|||
* @return string the heading
|
||||
*/
|
||||
public function get_heading($adding = false){
|
||||
$name = $this->name();
|
||||
if ($adding){
|
||||
if ($adding) {
|
||||
$action = 'adding';
|
||||
} else {
|
||||
$action = 'editing';
|
||||
}
|
||||
return get_string($action . $name, 'qtype_' . $name);
|
||||
return get_string($action . $this->name(), $this->plugin_name());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,34 +319,37 @@ class question_type {
|
|||
* redisplayed with validation errors, from validation_errors field, which
|
||||
* is itself an object, shown next to the form fields. (I don't think this is accurate any more.)
|
||||
*/
|
||||
public function save_question($question, $form, $course) {
|
||||
global $USER;
|
||||
function save_question($question, $form) {
|
||||
global $USER, $DB, $OUTPUT;
|
||||
|
||||
list($question->category) = explode(',', $form->category);
|
||||
$context = $this->get_context_by_category_id($question->category);
|
||||
|
||||
// This default implementation is suitable for most
|
||||
// question types.
|
||||
|
||||
// First, save the basic question itself
|
||||
$question->name = trim($form->name);
|
||||
$question->questiontext = trim($form->questiontext);
|
||||
$question->questiontextformat = $form->questiontextformat;
|
||||
$question->parent = isset($form->parent) ? $form->parent : 0;
|
||||
$question->length = $this->actual_number_of_questions($question);
|
||||
$question->penalty = isset($form->penalty) ? $form->penalty : 0;
|
||||
|
||||
if (empty($form->image)) {
|
||||
$question->image = '';
|
||||
if (empty($form->questiontext['text'])) {
|
||||
$question->questiontext = '';
|
||||
} else {
|
||||
$question->image = $form->image;
|
||||
$question->questiontext = trim($form->questiontext['text']);;
|
||||
}
|
||||
$question->questiontextformat = !empty($form->questiontext['format'])?$form->questiontext['format']:0;
|
||||
|
||||
if (empty($form->generalfeedback)) {
|
||||
if (empty($form->generalfeedback['text'])) {
|
||||
$question->generalfeedback = '';
|
||||
} else {
|
||||
$question->generalfeedback = trim($form->generalfeedback);
|
||||
$question->generalfeedback = trim($form->generalfeedback['text']);
|
||||
}
|
||||
$question->generalfeedbackformat = !empty($form->generalfeedback['format'])?$form->generalfeedback['format']:0;
|
||||
|
||||
if (empty($question->name)) {
|
||||
$question->name = shorten_text(strip_tags($question->questiontext), 15);
|
||||
$question->name = shorten_text(strip_tags($form->questiontext['text']), 15);
|
||||
if (empty($question->name)) {
|
||||
$question->name = '-';
|
||||
}
|
||||
|
@ -302,39 +363,42 @@ class question_type {
|
|||
$question->defaultmark = $form->defaultmark;
|
||||
}
|
||||
|
||||
list($question->category) = explode(',', $form->category);
|
||||
|
||||
if (!empty($question->id)) {
|
||||
/// Question already exists, update.
|
||||
$question->modifiedby = $USER->id;
|
||||
$question->timemodified = time();
|
||||
if (!update_record('question', $question)) {
|
||||
error('Could not update question!');
|
||||
}
|
||||
|
||||
} else {
|
||||
/// New question.
|
||||
// If the question is new, create it.
|
||||
if (empty($question->id)) {
|
||||
// Set the unique code
|
||||
$question->stamp = make_unique_id_code();
|
||||
$question->createdby = $USER->id;
|
||||
$question->modifiedby = $USER->id;
|
||||
$question->timecreated = time();
|
||||
$question->id = $DB->insert_record('question', $question);
|
||||
}
|
||||
|
||||
// Now, whether we are updating a existing question, or creating a new
|
||||
// one, we have to do the files processing and update the record.
|
||||
/// Question already exists, update.
|
||||
$question->modifiedby = $USER->id;
|
||||
$question->timemodified = time();
|
||||
if (!$question->id = insert_record('question', $question)) {
|
||||
error('Could not insert new question!');
|
||||
|
||||
if (!empty($question->questiontext) && !empty($form->questiontext['itemid'])) {
|
||||
$question->questiontext = file_save_draft_area_files($form->questiontext['itemid'], $context->id, 'question', 'questiontext', (int)$question->id, $this->fileoptions, $question->questiontext);
|
||||
}
|
||||
if (!empty($question->generalfeedback) && !empty($form->generalfeedback['itemid'])) {
|
||||
$question->generalfeedback = file_save_draft_area_files($form->generalfeedback['itemid'], $context->id, 'question', 'generalfeedback', (int)$question->id, $this->fileoptions, $question->generalfeedback);
|
||||
}
|
||||
$DB->update_record('question', $question);
|
||||
|
||||
// Now to save all the answers and type-specific options
|
||||
$form->id = $question->id;
|
||||
$form->qtype = $question->qtype;
|
||||
$form->category = $question->category;
|
||||
$form->questiontext = $question->questiontext;
|
||||
$form->questiontextformat = $question->questiontextformat;
|
||||
// current context
|
||||
$form->context = $context;
|
||||
|
||||
$result = $this->save_question_options($form);
|
||||
|
||||
if (!empty($result->error)) {
|
||||
error($result->error);
|
||||
print_error($result->error);
|
||||
}
|
||||
|
||||
if (!empty($result->notice)) {
|
||||
|
@ -342,16 +406,11 @@ class question_type {
|
|||
}
|
||||
|
||||
if (!empty($result->noticeyesno)) {
|
||||
notice_yesno($result->noticeyesno, "question.php?id=$question->id&courseid={$course->id}",
|
||||
"edit.php?courseid={$course->id}");
|
||||
print_footer($course);
|
||||
exit;
|
||||
throw new coding_exception('$result->noticeyesno no longer supported in save_question.');
|
||||
}
|
||||
|
||||
// Give the question a unique version stamp determined by question_hash()
|
||||
if (!set_field('question', 'version', question_hash($question), 'id', $question->id)) {
|
||||
error('Could not update question version field');
|
||||
}
|
||||
$DB->set_field('question', 'version', question_hash($question), array('id' => $question->id));
|
||||
|
||||
return $question;
|
||||
}
|
||||
|
@ -365,6 +424,7 @@ class question_type {
|
|||
* it is not a standard question object.
|
||||
*/
|
||||
public function save_question_options($question) {
|
||||
global $DB;
|
||||
$extra_question_fields = $this->extra_question_fields();
|
||||
|
||||
if (is_array($extra_question_fields)) {
|
||||
|
@ -372,7 +432,7 @@ class question_type {
|
|||
|
||||
$function = 'update_record';
|
||||
$questionidcolname = $this->questionid_column_name();
|
||||
$options = get_record($question_extension_table, $questionidcolname, $question->id);
|
||||
$options = $DB->get_record($question_extension_table, array($questionidcolname => $question->id));
|
||||
if (!$options) {
|
||||
$function = 'insert_record';
|
||||
$options = new stdClass;
|
||||
|
@ -388,7 +448,7 @@ class question_type {
|
|||
$options->$field = $question->$field;
|
||||
}
|
||||
|
||||
if (!$function($question_extension_table, $options)) {
|
||||
if (!$DB->{$function}($question_extension_table, $options)) {
|
||||
$result = new stdClass;
|
||||
$result->error = 'Could not save question options for ' .
|
||||
$this->name() . ' question id ' . $question->id;
|
||||
|
@ -403,7 +463,8 @@ class question_type {
|
|||
}
|
||||
|
||||
public function save_hints($formdata, $withparts = false) {
|
||||
delete_records('question_hints', 'questionid', $formdata->id);
|
||||
global $DB;
|
||||
$DB->delete_records('question_hints', array('questionid' => $formdata->id));
|
||||
|
||||
if (!empty($formdata->hint)) {
|
||||
$numhints = max(array_keys($formdata->hint)) + 1;
|
||||
|
@ -443,7 +504,7 @@ class question_type {
|
|||
continue;
|
||||
}
|
||||
|
||||
insert_record('question_hints', $hint);
|
||||
$DB->insert_record('question_hints', $hint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,18 +521,22 @@ class question_type {
|
|||
* specific information (it is passed by reference).
|
||||
*/
|
||||
public function get_question_options($question) {
|
||||
global $CFG;
|
||||
global $CFG, $DB, $OUTPUT;
|
||||
|
||||
if (!isset($question->options)) {
|
||||
$question->options = new stdClass();
|
||||
}
|
||||
|
||||
$extra_question_fields = $this->extra_question_fields();
|
||||
if (is_array($extra_question_fields)) {
|
||||
$question_extension_table = array_shift($extra_question_fields);
|
||||
$extra_data = get_record($question_extension_table, $this->questionid_column_name(), $question->id, '', '', '', '', implode(', ', $extra_question_fields));
|
||||
$extra_data = $DB->get_record($question_extension_table, array($this->questionid_column_name() => $question->id), implode(', ', $extra_question_fields));
|
||||
if ($extra_data) {
|
||||
foreach ($extra_question_fields as $field) {
|
||||
$question->options->$field = $extra_data->$field;
|
||||
}
|
||||
} else {
|
||||
notify("Failed to load question options from the table $question_extension_table for questionid " .
|
||||
echo $OUTPUT->notification("Failed to load question options from the table $question_extension_table for questionid " .
|
||||
$question->id);
|
||||
return false;
|
||||
}
|
||||
|
@ -480,21 +545,21 @@ class question_type {
|
|||
$extra_answer_fields = $this->extra_answer_fields();
|
||||
if (is_array($extra_answer_fields)) {
|
||||
$answer_extension_table = array_shift($extra_answer_fields);
|
||||
$question->options->answers = get_records_sql('
|
||||
SELECT qa.*, qax.' . implode(', qax.', $extra_answer_fields) . '
|
||||
FROM ' . $CFG->prefix . 'question_answers qa, ' . $CFG->prefix . '$answer_extension_table qax
|
||||
WHERE qa.questionid = ' . $question->id . ' AND qax.answerid = qa.id');
|
||||
$question->options->answers = $DB->get_records_sql("
|
||||
SELECT qa.*, qax." . implode(', qax.', $extra_answer_fields) . "
|
||||
FROM {question_answers} qa, {$answer_extension_table} qax
|
||||
WHERE qa.questionid = ? AND qax.answerid = qa.id", array($question->id));
|
||||
if (!$question->options->answers) {
|
||||
notify("Failed to load question answers from the table $answer_extension_table for questionid " .
|
||||
echo $OUTPUT->notification("Failed to load question answers from the table $answer_extension_table for questionid " .
|
||||
$question->id);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Don't check for success or failure because some question types do not use the answers table.
|
||||
$question->options->answers = get_records('question_answers', 'question', $question->id, 'id ASC');
|
||||
$question->options->answers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC');
|
||||
}
|
||||
|
||||
$question->hints = get_records('question_hints', 'questionid', $question->id, 'id ASC');
|
||||
$question->hints = $DB->get_records('question_hints', array('questionid' => $question->id), 'id ASC');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -592,34 +657,32 @@ class question_type {
|
|||
}
|
||||
|
||||
/**
|
||||
* Deletes a question from the question-type specific tables
|
||||
*
|
||||
* @return boolean Success/Failure
|
||||
* @param object $question The question being deleted
|
||||
* Deletes the question-type specific data when a question is deleted.
|
||||
* @param integer $question the question being deleted.
|
||||
* @param integer $contextid the context this quesiotn belongs to.
|
||||
*/
|
||||
public function delete_question($questionid) {
|
||||
global $CFG;
|
||||
$success = true;
|
||||
public function delete_question($questionid, $contextid) {
|
||||
global $DB;
|
||||
|
||||
$this->delete_files($questionid, $contextid);
|
||||
|
||||
$extra_question_fields = $this->extra_question_fields();
|
||||
if (is_array($extra_question_fields)) {
|
||||
$question_extension_table = array_shift($extra_question_fields);
|
||||
$success = $success && delete_records($question_extension_table,
|
||||
$this->questionid_column_name(), $questionid);
|
||||
$DB->delete_records($question_extension_table,
|
||||
array($this->questionid_column_name() => $questionid));
|
||||
}
|
||||
|
||||
$extra_answer_fields = $this->extra_answer_fields();
|
||||
if (is_array($extra_answer_fields)) {
|
||||
$answer_extension_table = array_shift($extra_answer_fields);
|
||||
$success = $success && delete_records_select($answer_extension_table,
|
||||
"answerid IN (SELECT qa.id FROM {$CFG->prefix}question_answers qa WHERE qa.question = $questionid)");
|
||||
$DB->delete_records_select($answer_extension_table,
|
||||
"answerid IN (SELECT qa.id FROM {question_answers} qa WHERE qa.question = ?)", array($questionid));
|
||||
}
|
||||
|
||||
$success = $success && delete_records('question_answers', 'question', $questionid);
|
||||
$DB->delete_records('question_answers', array('question' => $questionid));
|
||||
|
||||
$success = $success && delete_records('question_hints', 'questionid', $questionid);
|
||||
|
||||
return $success;
|
||||
$DB->delete_records('question_hints', array('questionid' => $questionid));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -681,53 +744,35 @@ class question_type {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return any CSS JavaScript required on the head of the question editing
|
||||
* page question/question.php.
|
||||
*
|
||||
* @return an array of bits of HTML to add to the head of pages where
|
||||
* this question is displayed in the body. The array should use
|
||||
* integer array keys, which have no significance.
|
||||
* Like @see{get_html_head_contributions}, but this method is for CSS and
|
||||
* JavaScript required on the question editing page question/question.php.
|
||||
*/
|
||||
public function get_editing_head_contributions() {
|
||||
// By default, we link to any of the files styles.css, styles.php,
|
||||
// script.js or script.php that exist in the plugin folder.
|
||||
// Core question types should not use this mechanism. Their styles
|
||||
// should be included in the standard theme.
|
||||
return $this->find_standard_scripts_and_css();
|
||||
$this->find_standard_scripts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method used by @see{get_editing_head_contributions} and
|
||||
* Utility method used by @see{get_html_head_contributions} and
|
||||
* @see{get_editing_head_contributions}. This looks for any of the files
|
||||
* styles.css, styles.php, script.js or script.php that exist in the plugin
|
||||
* folder and ensures they get included.
|
||||
*
|
||||
* @return array as required by get_editing_head_contributions.
|
||||
* script.js or script.php that exist in the plugin folder and ensures they
|
||||
* get included.
|
||||
*/
|
||||
public function find_standard_scripts_and_css() {
|
||||
protected function find_standard_scripts() {
|
||||
global $PAGE;
|
||||
|
||||
$plugindir = $this->plugin_dir();
|
||||
$baseurl = $this->plugin_baseurl();
|
||||
$plugindirrel = 'question/type/' . $this->name();
|
||||
|
||||
if (file_exists($plugindir . '/script.js')) {
|
||||
require_js($baseurl . '/script.js');
|
||||
$PAGE->requires->js('/' . $plugindirrel . '/script.js');
|
||||
}
|
||||
if (file_exists($plugindir . '/script.php')) {
|
||||
require_js($baseurl . '/script.php');
|
||||
$PAGE->requires->js('/' . $plugindirrel . '/script.php');
|
||||
}
|
||||
|
||||
$stylesheets = array();
|
||||
if (file_exists($plugindir . '/styles.css')) {
|
||||
$stylesheets[] = 'styles.css';
|
||||
}
|
||||
if (file_exists($plugindir . '/styles.php')) {
|
||||
$stylesheets[] = 'styles.php';
|
||||
}
|
||||
$contributions = array();
|
||||
foreach ($stylesheets as $stylesheet) {
|
||||
$contributions[] = '<link rel="stylesheet" type="text/css" href="' .
|
||||
$baseurl . '/' . $stylesheet . '" />';
|
||||
}
|
||||
return $contributions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -746,223 +791,11 @@ class question_type {
|
|||
*
|
||||
* @return boolean Whether the wizard's last page was submitted or not.
|
||||
*/
|
||||
public function finished_edit_wizard(&$form) {
|
||||
public function finished_edit_wizard($form) {
|
||||
//In the default case there is only one edit page.
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find all course / site files linked from a question.
|
||||
*
|
||||
* Need to check for links to files in question_answers.answer and feedback
|
||||
* and in question table in generalfeedback and questiontext fields. Methods
|
||||
* on child classes will also check extra question specific fields.
|
||||
*
|
||||
* Needs to be overriden for child classes that have extra fields containing
|
||||
* html.
|
||||
*
|
||||
* @param string html the html to search
|
||||
* @param int courseid search for files for courseid course or set to siteid for
|
||||
* finding site files.
|
||||
* @return array of url, relative url is key and array with one item = question id as value
|
||||
* relative url is relative to course/site files directory root.
|
||||
*/
|
||||
public function find_file_links($question, $courseid){
|
||||
$urls = array();
|
||||
|
||||
/// Question image
|
||||
if ($question->image != ''){
|
||||
if (substr(strtolower($question->image), 0, 7) == 'http://') {
|
||||
$matches = array();
|
||||
|
||||
//support for older questions where we have a complete url in image field
|
||||
if (preg_match('!^'.question_file_links_base_url($courseid).'(.*)!i', $question->image, $matches)){
|
||||
if ($cleanedurl = question_url_check($urls[$matches[2]])){
|
||||
$urls[$cleanedurl] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($question->image != ''){
|
||||
if ($cleanedurl = question_url_check($question->image)){
|
||||
$urls[$cleanedurl] = null;//will be set later
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Questiontext and general feedback.
|
||||
$urls += question_find_file_links_from_html($question->questiontext, $courseid);
|
||||
$urls += question_find_file_links_from_html($question->generalfeedback, $courseid);
|
||||
|
||||
/// Answers, if this question uses them.
|
||||
if (isset($question->options->answers)){
|
||||
foreach ($question->options->answers as $answerkey => $answer){
|
||||
/// URLs in the answers themselves, if appropriate.
|
||||
if ($this->has_html_answers()) {
|
||||
$urls += question_find_file_links_from_html($answer->answer, $courseid);
|
||||
}
|
||||
/// URLs in the answer feedback.
|
||||
$urls += question_find_file_links_from_html($answer->feedback, $courseid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set all the values of the array to the question object
|
||||
if ($urls){
|
||||
$urls = array_combine(array_keys($urls), array_fill(0, count($urls), array($question->id)));
|
||||
}
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find all course / site files linked from a question.
|
||||
*
|
||||
* Need to check for links to files in question_answers.answer and feedback
|
||||
* and in question table in generalfeedback and questiontext fields. Methods
|
||||
* on child classes will also check extra question specific fields.
|
||||
*
|
||||
* Needs to be overriden for child classes that have extra fields containing
|
||||
* html.
|
||||
*
|
||||
* @param string html the html to search
|
||||
* @param int course search for files for courseid course or set to siteid for
|
||||
* finding site files.
|
||||
* @return array of files, file name is key and array with one item = question id as value
|
||||
*/
|
||||
public function replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination){
|
||||
global $CFG;
|
||||
$updateqrec = false;
|
||||
|
||||
/// Question image
|
||||
if (!empty($question->image)){
|
||||
//support for older questions where we have a complete url in image field
|
||||
if (substr(strtolower($question->image), 0, 7) == 'http://') {
|
||||
$questionimage = preg_replace('!^'.question_file_links_base_url($fromcourseid).preg_quote($url, '!').'$!i', $destination, $question->image, 1);
|
||||
} else {
|
||||
$questionimage = preg_replace('!^'.preg_quote($url, '!').'$!i', $destination, $question->image, 1);
|
||||
}
|
||||
if ($questionimage != $question->image){
|
||||
$question->image = $questionimage;
|
||||
$updateqrec = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Questiontext and general feedback.
|
||||
$question->questiontext = question_replace_file_links_in_html($question->questiontext, $fromcourseid, $tocourseid, $url, $destination, $updateqrec);
|
||||
$question->generalfeedback = question_replace_file_links_in_html($question->generalfeedback, $fromcourseid, $tocourseid, $url, $destination, $updateqrec);
|
||||
|
||||
/// If anything has changed, update it in the database.
|
||||
if ($updateqrec){
|
||||
if (!update_record('question', addslashes_recursive($question))){
|
||||
error ('Couldn\'t update question '.$question->name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Answers, if this question uses them.
|
||||
if (isset($question->options->answers)){
|
||||
//answers that do not need updating have been unset
|
||||
foreach ($question->options->answers as $answer){
|
||||
$answerchanged = false;
|
||||
/// URLs in the answers themselves, if appropriate.
|
||||
if ($this->has_html_answers()) {
|
||||
$answer->answer = question_replace_file_links_in_html($answer->answer, $fromcourseid, $tocourseid, $url, $destination, $answerchanged);
|
||||
}
|
||||
/// URLs in the answer feedback.
|
||||
$answer->feedback = question_replace_file_links_in_html($answer->feedback, $fromcourseid, $tocourseid, $url, $destination, $answerchanged);
|
||||
/// If anything has changed, update it in the database.
|
||||
if ($answerchanged){
|
||||
if (!update_record('question_answers', addslashes_recursive($answer))){
|
||||
error ('Couldn\'t update question ('.$question->name.') answer '.$answer->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// BACKUP FUNCTIONS ////////////////////////////
|
||||
|
||||
/*
|
||||
* Backup the data in the question
|
||||
*
|
||||
* This is used in question/backuplib.php
|
||||
*/
|
||||
public function backup($bf,$preferences,$question,$level=6) {
|
||||
|
||||
$status = true;
|
||||
$extraquestionfields = $this->extra_question_fields();
|
||||
|
||||
if (is_array($extraquestionfields)) {
|
||||
$questionextensiontable = array_shift($extraquestionfields);
|
||||
$record = get_record($questionextensiontable, $this->questionid_column_name(), $question);
|
||||
if ($record) {
|
||||
$tagname = strtoupper($this->name());
|
||||
$status = $status && fwrite($bf, start_tag($tagname, $level, true));
|
||||
foreach ($extraquestionfields as $field) {
|
||||
if (!isset($record->$field)) {
|
||||
echo "No data for field $field when backuping " .
|
||||
$this->name() . ' question id ' . $question;
|
||||
return false;
|
||||
}
|
||||
fwrite($bf, full_tag(strtoupper($field), $level + 1, false, $record->$field));
|
||||
}
|
||||
$status = $status && fwrite($bf, end_tag($tagname, $level, true));
|
||||
}
|
||||
}
|
||||
|
||||
$extraasnwersfields = $this->extra_answer_fields();
|
||||
if (is_array($extraasnwersfields)) {
|
||||
//TODO backup the answers, with any extra data.
|
||||
} else {
|
||||
$status = $status && question_backup_answers($bf, $preferences, $question);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/// RESTORE FUNCTIONS /////////////////
|
||||
|
||||
/*
|
||||
* Restores the data in the question
|
||||
*
|
||||
* This is used in question/restorelib.php
|
||||
*/
|
||||
public function restore($old_question_id,$new_question_id,$info,$restore) {
|
||||
|
||||
$status = true;
|
||||
$extraquestionfields = $this->extra_question_fields();
|
||||
|
||||
if (is_array($extraquestionfields)) {
|
||||
$questionextensiontable = array_shift($extraquestionfields);
|
||||
$tagname = strtoupper($this->name());
|
||||
$recordinfo = $info['#'][$tagname][0];
|
||||
|
||||
$record = new stdClass;
|
||||
$qidcolname = $this->questionid_column_name();
|
||||
$record->$qidcolname = $new_question_id;
|
||||
foreach ($extraquestionfields as $field) {
|
||||
$record->$field = backup_todb($recordinfo['#'][strtoupper($field)]['0']['#']);
|
||||
}
|
||||
if (!insert_record($questionextensiontable, $record)) {
|
||||
echo "Can't insert record in $questionextensiontable when restoring " .
|
||||
$this->name() . ' question id ' . $question;
|
||||
$status = false;
|
||||
}
|
||||
}
|
||||
//TODO restore extra data in answers
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function restore_map($old_question_id,$new_question_id,$info,$restore) {
|
||||
// There is nothing to decode
|
||||
return true;
|
||||
}
|
||||
|
||||
public function restore_recode_answer($state, $restore) {
|
||||
// There is nothing to decode
|
||||
return $state->answer;
|
||||
}
|
||||
|
||||
/// IMPORT/EXPORT FUNCTIONS /////////////////
|
||||
|
||||
/*
|
||||
|
@ -988,7 +821,7 @@ class question_type {
|
|||
$qo->qtype = $question_type;
|
||||
|
||||
foreach ($extraquestionfields as $field) {
|
||||
$qo->$field = addslashes($format->getpath($data, array('#',$field,0,'#'), $qo->$field));
|
||||
$qo->$field = $format->getpath($data, array('#',$field,0,'#'), $qo->$field);
|
||||
}
|
||||
|
||||
// run through the answers
|
||||
|
@ -1072,6 +905,142 @@ class question_type {
|
|||
$question->qtype = $this->qtype;
|
||||
return array($form, $question);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get question context by category id
|
||||
* @param int $category
|
||||
* @return object $context
|
||||
*/
|
||||
function get_context_by_category_id($category) {
|
||||
global $DB;
|
||||
$contextid = $DB->get_field('question_categories', 'contextid', array('id'=>$category));
|
||||
$context = get_context_instance_by_id($contextid);
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the file belonging to one text field.
|
||||
*
|
||||
* @param array $field the data from the form (or from import). This will
|
||||
* normally have come from the formslib editor element, so it will be an
|
||||
* array with keys 'text', 'format' and 'itemid'. However, when we are
|
||||
* importing, it will be an array with keys 'text', 'format' and 'files'
|
||||
* @param object $context the context the question is in.
|
||||
* @param string $component indentifies the file area question.
|
||||
* @param string $filearea indentifies the file area questiontext, generalfeedback,answerfeedback.
|
||||
* @param integer $itemid identifies the file area.
|
||||
*
|
||||
* @return string the text for this field, after files have been processed.
|
||||
*/
|
||||
protected function import_or_save_files($field, $context, $component, $filearea, $itemid) {
|
||||
if (!empty($field['itemid'])) {
|
||||
// This is the normal case. We are safing the questions editing form.
|
||||
return file_save_draft_area_files($field['itemid'], $context->id, $component,
|
||||
$filearea, $itemid, $this->fileoptions, trim($field['text']));
|
||||
|
||||
} else if (!empty($field['files'])) {
|
||||
// This is the case when we are doing an import.
|
||||
foreach ($field['files'] as $file) {
|
||||
$this->import_file($context, $component, $filearea, $itemid, $file);
|
||||
}
|
||||
}
|
||||
return trim($field['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all the files belonging to this question from one context to another.
|
||||
* @param integer $questionid the question being moved.
|
||||
* @param integer $oldcontextid the context it is moving from.
|
||||
* @param integer $newcontextid the context it is moving to.
|
||||
*/
|
||||
public function move_files($questionid, $oldcontextid, $newcontextid) {
|
||||
$fs = get_file_storage();
|
||||
$fs->move_area_files_to_new_context($oldcontextid,
|
||||
$newcontextid, 'question', 'questiontext', $questionid);
|
||||
$fs->move_area_files_to_new_context($oldcontextid,
|
||||
$newcontextid, 'question', 'generalfeedback', $questionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all the files belonging to this question's answers when the question
|
||||
* is moved from one context to another.
|
||||
* @param integer $questionid the question being moved.
|
||||
* @param integer $oldcontextid the context it is moving from.
|
||||
* @param integer $newcontextid the context it is moving to.
|
||||
* @param boolean $answerstoo whether there is an 'answer' question area,
|
||||
* as well as an 'answerfeedback' one. Default false.
|
||||
*/
|
||||
protected function move_files_in_answers($questionid, $oldcontextid, $newcontextid, $answerstoo = false) {
|
||||
global $DB;
|
||||
$fs = get_file_storage();
|
||||
|
||||
$answerids = $DB->get_records_menu('question_answers',
|
||||
array('question' => $questionid), 'id', 'id,1');
|
||||
foreach ($answerids as $answerid => $notused) {
|
||||
if ($answerstoo) {
|
||||
$fs->move_area_files_to_new_context($oldcontextid,
|
||||
$newcontextid, 'question', 'answer', $answerid);
|
||||
}
|
||||
$fs->move_area_files_to_new_context($oldcontextid,
|
||||
$newcontextid, 'question', 'answerfeedback', $answerid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the files belonging to this question.
|
||||
* @param integer $questionid the question being deleted.
|
||||
* @param integer $contextid the context the question is in.
|
||||
*/
|
||||
protected function delete_files($questionid, $contextid) {
|
||||
$fs = get_file_storage();
|
||||
$fs->delete_area_files($contextid, 'question', 'questiontext', $questionid);
|
||||
$fs->delete_area_files($contextid, 'question', 'generalfeedback', $questionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the files belonging to this question's answers.
|
||||
* @param integer $questionid the question being deleted.
|
||||
* @param integer $contextid the context the question is in.
|
||||
* @param boolean $answerstoo whether there is an 'answer' question area,
|
||||
* as well as an 'answerfeedback' one. Default false.
|
||||
*/
|
||||
protected function delete_files_in_answers($questionid, $contextid, $answerstoo = false) {
|
||||
global $DB;
|
||||
$fs = get_file_storage();
|
||||
|
||||
$answerids = $DB->get_records_menu('question_answers',
|
||||
array('question' => $questionid), 'id', 'id,1');
|
||||
foreach ($answerids as $answerid => $notused) {
|
||||
if ($answerstoo) {
|
||||
$fs->delete_area_files($contextid, 'question', 'answer', $answerid);
|
||||
}
|
||||
$fs->delete_area_files($contextid, 'question', 'answerfeedback', $answerid);
|
||||
}
|
||||
}
|
||||
|
||||
function import_file($context, $component, $filearea, $itemid, $file) {
|
||||
$fs = get_file_storage();
|
||||
$record = new stdclass;
|
||||
if (is_object($context)) {
|
||||
$record->contextid = $context->id;
|
||||
} else {
|
||||
$record->contextid = $context;
|
||||
}
|
||||
$record->component = $component;
|
||||
$record->filearea = $filearea;
|
||||
$record->itemid = $itemid;
|
||||
$record->filename = $file->name;
|
||||
$record->filepath = '/';
|
||||
return $fs->create_file_from_string($record, $this->decode_file($file));
|
||||
}
|
||||
|
||||
function decode_file($file) {
|
||||
switch ($file->encoding) {
|
||||
case 'base64':
|
||||
default:
|
||||
return base64_decode($file->content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ abstract class qtype_renderer extends renderer_base {
|
|||
* @return string html fragment.
|
||||
*/
|
||||
function feedback_image($fraction, $selected = true) {
|
||||
global $CFG;
|
||||
global $OUTPUT;
|
||||
|
||||
$state = question_state::graded_state_for_fraction($fraction);
|
||||
|
||||
|
@ -218,7 +218,7 @@ abstract class qtype_renderer extends renderer_base {
|
|||
}
|
||||
|
||||
$attributes = array(
|
||||
'src' => $CFG->pixpath . '/i/' . $icon . '.gif',
|
||||
'src' => $OUTPUT->pix_url('i/' . $icon),
|
||||
'alt' => get_string($state->get_feedback_class(), 'question'),
|
||||
'class' => 'questioncorrectnessicon',
|
||||
);
|
||||
|
|
|
@ -1,74 +1,44 @@
|
|||
<?php
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// NOTICE OF COPYRIGHT //
|
||||
// //
|
||||
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
|
||||
// http://moodle.org //
|
||||
// //
|
||||
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation; either version 2 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License for more details: //
|
||||
// //
|
||||
// http://www.gnu.org/copyleft/gpl.html //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
/**
|
||||
* Tests for some of ../questiontype.php
|
||||
* Unit tests for the question type base class.
|
||||
*
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package moodlecore
|
||||
* @subpackage questiontypes
|
||||
* @copyright 2008 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
if (!defined('MOODLE_INTERNAL')) {
|
||||
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
|
||||
}
|
||||
|
||||
require_once($CFG->dirroot . '/question/type/questiontype.php');
|
||||
|
||||
class default_questiontype_test extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests for some of ../questionbase.php
|
||||
*
|
||||
* @copyright 2008 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_type_test extends UnitTestCase {
|
||||
public static $includecoverage = array('question/type/questiontype.php');
|
||||
protected $qtype;
|
||||
|
||||
public function setUp() {
|
||||
$this->qtype = new default_questiontype();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
$this->qtype = null;
|
||||
}
|
||||
|
||||
function test_compare_responses() {
|
||||
$question = new stdClass;
|
||||
$state = new stdClass;
|
||||
$teststate = new stdClass;
|
||||
|
||||
$state->responses = array();
|
||||
$teststate->responses = array();
|
||||
$this->assertTrue($this->qtype->compare_responses($question, $state, $teststate));
|
||||
|
||||
$state->responses = array('' => 'frog');
|
||||
$teststate->responses = array('' => 'toad');
|
||||
$this->assertFalse($this->qtype->compare_responses($question, $state, $teststate));
|
||||
|
||||
$state->responses = array('x' => 'frog');
|
||||
$teststate->responses = array('y' => 'frog');
|
||||
$this->assertFalse($this->qtype->compare_responses($question, $state, $teststate));
|
||||
|
||||
$state->responses = array(1 => 1, 2 => 2);
|
||||
$teststate->responses = array(2 => 2, 1 => 1);
|
||||
$this->assertTrue($this->qtype->compare_responses($question, $state, $teststate));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue