migrated 'missingtype' questoin creation forms to use formslib
[moodle.git] / question / type / questiontype.php
CommitLineData
516cf3eb 1<?php // $Id$
2/**
3* The default questiontype class.
4*
5* @version $Id$
6* @author Martin Dougiamas and many others. This has recently been completely
7* rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
8* the Serving Mathematics project
9* {@link http://maths.york.ac.uk/serving_maths}
10* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
11* @package quiz
8d205441 12*//** */
516cf3eb 13
8d205441 14require_once($CFG->libdir . '/questionlib.php');
516cf3eb 15
32b0adfa 16/**
17 * This is the base class for Moodle question types.
271ffe3f 18 *
32b0adfa 19 * There are detailed comments on each method, explaining what the method is
20 * for, and the circumstances under which you might need to override it.
271ffe3f 21 *
32b0adfa 22 * Note: the questiontype API should NOT be considered stable yet. Very few
23 * question tyeps have been produced yet, so we do not yet know all the places
271ffe3f 24 * where the current API is insufficient. I would rather learn from the
32b0adfa 25 * experiences of the first few question type implementors, and improve the
26 * interface to meet their needs, rather the freeze the API prematurely and
27 * condem everyone to working round a clunky interface for ever afterwards.
28 */
af3830ee 29class default_questiontype {
516cf3eb 30
31 /**
a2156789 32 * Name of the question type
33 *
34 * The name returned should coincide with the name of the directory
35 * in which this questiontype is located
271ffe3f 36 *
32b0adfa 37 * @return string the name of this question type.
a2156789 38 */
516cf3eb 39 function name() {
40 return 'default';
41 }
42
a2156789 43 /**
271ffe3f 44 * The name this question should appear as in the create new question
a2156789 45 * dropdown.
271ffe3f 46 *
a2156789 47 * @return mixed the desired string, or false to hide this question type in the menu.
48 */
49 function menu_name() {
50 $name = $this->name();
51 $menu_name = get_string($name, 'qtype_' . $name);
52 if ($menu_name[0] == '[') {
271ffe3f 53 // Legacy behavior, if the string was not in the proper qtype_name
a2156789 54 // language file, look it up in the quiz one.
55 $menu_name = get_string($this->name(), 'quiz');
56 }
57 return $menu_name;
58 }
271ffe3f 59
a2156789 60 /**
61 * @return boolean true if this question can only be graded manually.
62 */
63 function is_manual_graded() {
64 return false;
65 }
66
67 /**
68 * @return boolean true if this question type can be used by the random question type.
69 */
70 function is_usable_by_random() {
71 return true;
72 }
73
36703ed7 74 /**
75 * Return an instance of the question editing form definition. This looks for a
76 * class called edit_{$this->name()}_question_form in the file
77 * {$CFG->docroot}/question/type/{$this->name()}/edit_{$this->name()}_question_form.php
78 * and if it exists returns an instance of it.
79 *
80 * @param string $submiturl passed on to the constructor call.
81 * @return object an instance of the form definition, or null if one could not be found.
82 */
271ffe3f 83 function create_editing_form($submiturl, $question) {
36703ed7 84 global $CFG;
85 require_once("{$CFG->dirroot}/question/type/edit_question_form.php");
271ffe3f 86 $definition_file = $CFG->dirroot.'/question/type/'.$this->name().'/edit_'.$this->name().'_form.php';
36703ed7 87 if (!(is_readable($definition_file) && is_file($definition_file))) {
88 return null;
89 }
90 require_once($definition_file);
271ffe3f 91 $classname = 'question_edit_'.$this->name().'_form';
36703ed7 92 if (!class_exists($classname)) {
93 return null;
94 }
271ffe3f 95 return new $classname($submiturl, $question);
36703ed7 96 }
97
98 /**
99 *
100 *
101 * @param $question
102 */
103 function set_default_options(&$question) {
36703ed7 104 }
105
516cf3eb 106 /**
107 * Saves or updates a question after editing by a teacher
108 *
109 * Given some question info and some data about the answers
110 * this function parses, organises and saves the question
111 * It is used by {@link question.php} when saving new data from
112 * a form, and also by {@link import.php} when importing questions
113 * This function in turn calls {@link save_question_options}
114 * to save question-type specific options
32b0adfa 115 * @param object $question the question object which should be updated
116 * @param object $form the form submitted by the teacher
117 * @param object $course the course we are in
695d225c 118 * @return object On success, return the new question object. On failure,
271ffe3f 119 * return an object as follows. If the error object has an errors field,
32b0adfa 120 * display that as an error message. Otherwise, the editing form will be
695d225c 121 * redisplayed with validation errors, from validation_errors field, which
122 * is itself an object, shown next to the form fields.
516cf3eb 123 */
124 function save_question($question, $form, $course) {
125 // This default implementation is suitable for most
126 // question types.
271ffe3f 127
516cf3eb 128 // First, save the basic question itself
516cf3eb 129 $question->name = trim($form->name);
130 $question->questiontext = trim($form->questiontext);
131 $question->questiontextformat = $form->questiontextformat;
132 $question->parent = isset($form->parent)? $form->parent : 0;
133 $question->length = $this->actual_number_of_questions($question);
134 $question->penalty = isset($form->penalty) ? $form->penalty : 0;
135
136 if (empty($form->image)) {
137 $question->image = "";
138 } else {
139 $question->image = $form->image;
140 }
141
a4514d91 142 if (empty($form->generalfeedback)) {
143 $question->generalfeedback = '';
1b8a7434 144 } else {
a4514d91 145 $question->generalfeedback = trim($form->generalfeedback);
1b8a7434 146 }
147
516cf3eb 148 if (empty($question->name)) {
5433ee83 149 $question->name = substr(strip_tags($question->questiontext), 0, 15);
516cf3eb 150 if (empty($question->name)) {
151 $question->name = '-';
152 }
153 }
154
155 if ($question->penalty > 1 or $question->penalty < 0) {
156 $question->errors['penalty'] = get_string('invalidpenalty', 'quiz');
157 }
158
159 if (isset($form->defaultgrade)) {
160 $question->defaultgrade = $form->defaultgrade;
161 }
162
516cf3eb 163 if (!empty($question->id)) { // Question already exists
cbe20043 164 // keep existing unique stamp code
165 $question->stamp = get_field('question', 'stamp', 'id', $question->id);
4f48fb42 166 if (!update_record("question", $question)) {
516cf3eb 167 error("Could not update question!");
168 }
169 } else { // Question is a new one
cbe20043 170 // Set the unique code
171 $question->stamp = make_unique_id_code();
4f48fb42 172 if (!$question->id = insert_record("question", $question)) {
516cf3eb 173 error("Could not insert new question!");
174 }
175 }
176
177 // Now to save all the answers and type-specific options
178
6459b8fc 179 $form->id = $question->id;
180 $form->qtype = $question->qtype;
516cf3eb 181 $form->category = $question->category;
6459b8fc 182 $form->questiontext = $question->questiontext;
516cf3eb 183
184 $result = $this->save_question_options($form);
185
186 if (!empty($result->error)) {
187 error($result->error);
188 }
189
190 if (!empty($result->notice)) {
191 notice($result->notice, "question.php?id=$question->id");
192 }
193
194 if (!empty($result->noticeyesno)) {
25ee4157 195 notice_yesno($result->noticeyesno, "question.php?id=$question->id&amp;courseid={$course->id}",
196 "edit.php?courseid={$course->id}");
516cf3eb 197 print_footer($course);
198 exit;
199 }
200
cbe20043 201 // Give the question a unique version stamp determined by question_hash()
202 if (!set_field('question', 'version', question_hash($question), 'id', $question->id)) {
203 error('Could not update question version field');
204 }
205
516cf3eb 206 return $question;
207 }
271ffe3f 208
516cf3eb 209 /**
210 * Saves question-type specific options
211 *
212 * This is called by {@link save_question()} to save the question-type specific data
213 * @return object $result->error or $result->noticeyesno or $result->notice
214 * @param object $question This holds the information from the editing form,
215 * it is not a standard question object.
216 */
217 function save_question_options($question) {
4eda4eec 218 return null;
516cf3eb 219 }
220
221 /**
222 * Changes all states for the given attempts over to a new question
223 *
224 * This is used by the versioning code if the teacher requests that a question
225 * gets replaced by the new version. In order for the attempts to be regraded
226 * properly all data in the states referring to the old question need to be
227 * changed to refer to the new version instead. In particular for question types
228 * that use the answers table the answers belonging to the old question have to
229 * be changed to those belonging to the new version.
230 *
231 * @param integer $oldquestionid The id of the old question
232 * @param object $newquestion The new question
233 * @param array $attempts An array of all attempt objects in whose states
234 * replacement should take place
235 */
236 function replace_question_in_attempts($oldquestionid, $newquestion, $attemtps) {
237 echo 'Not yet implemented';
238 return;
239 }
240
241 /**
242 * Loads the question type specific options for the question.
243 *
244 * This function loads any question type specific options for the
245 * question from the database into the question object. This information
246 * is placed in the $question->options field. A question type is
247 * free, however, to decide on a internal structure of the options field.
248 * @return bool Indicates success or failure.
249 * @param object $question The question object for the question. This object
250 * should be updated to include the question type
251 * specific information (it is passed by reference).
252 */
253 function get_question_options(&$question) {
254 if (!isset($question->options)) {
255 $question->options = new object;
256 }
257 // The default implementation attaches all answers for this question
ed28abca 258 $question->options->answers = get_records('question_answers', 'question',
259 $question->id);
516cf3eb 260 return true;
261 }
262
263 /**
0429cd86 264 * Deletes states from the question-type specific tables
265 *
266 * @param string $stateslist Comma separated list of state ids to be deleted
267 */
268 function delete_states($stateslist) {
269 /// The default question type does not have any tables of its own
270 // therefore there is nothing to delete
271
272 return true;
273 }
274
275 /**
276 * Deletes a question from the question-type specific tables
516cf3eb 277 *
278 * @return boolean Success/Failure
279 * @param object $question The question being deleted
280 */
90c3f310 281 function delete_question($questionid) {
516cf3eb 282 /// The default question type does not have any tables of its own
283 // therefore there is nothing to delete
284
285 return true;
286 }
287
288 /**
289 * Returns the number of question numbers which are used by the question
290 *
291 * This function returns the number of question numbers to be assigned
292 * to the question. Most question types will have length one; they will be
dfa47f96 293 * assigned one number. The 'description' type, however does not use up a
516cf3eb 294 * number and so has a length of zero. Other question types may wish to
295 * handle a bundle of questions and hence return a number greater than one.
296 * @return integer The number of question numbers which should be
297 * assigned to the question.
298 * @param object $question The question whose length is to be determined.
299 * Question type specific information is included.
300 */
301 function actual_number_of_questions($question) {
302 // By default, each question is given one number
303 return 1;
304 }
305
306 /**
307 * Creates empty session and response information for the question
308 *
309 * This function is called to start a question session. Empty question type
310 * specific session data (if any) and empty response data will be added to the
311 * state object. Session data is any data which must persist throughout the
312 * attempt possibly with updates as the user interacts with the
313 * question. This function does NOT create new entries in the database for
314 * the session; a call to the {@link save_session_and_responses} member will
315 * occur to do this.
316 * @return bool Indicates success or failure.
317 * @param object $question The question for which the session is to be
318 * created. Question type specific information is
319 * included.
320 * @param object $state The state to create the session for. Note that
321 * this will not have been saved in the database so
322 * there will be no id. This object will be updated
323 * to include the question type specific information
324 * (it is passed by reference). In particular, empty
325 * responses will be created in the ->responses
326 * field.
327 * @param object $cmoptions
328 * @param object $attempt The attempt for which the session is to be
329 * started. Questions may wish to initialize the
330 * session in different ways depending on the user id
331 * or time available for the attempt.
332 */
333 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
334 // The default implementation should work for the legacy question types.
335 // Most question types with only a single form field for the student's response
336 // will use the empty string '' as the index for that one response. This will
337 // automatically be stored in and restored from the answer field in the
4f48fb42 338 // question_states table.
5a14d563 339 $state->responses = array(
340 '' => '',
341 );
516cf3eb 342 return true;
343 }
344
345 /**
346 * Restores the session data and most recent responses for the given state
347 *
348 * This function loads any session data associated with the question
349 * session in the given state from the database into the state object.
350 * In particular it loads the responses that have been saved for the given
351 * state into the ->responses member of the state object.
352 *
353 * Question types with only a single form field for the student's response
354 * will not need not restore the responses; the value of the answer
4f48fb42 355 * field in the question_states table is restored to ->responses['']
516cf3eb 356 * before this function is called. Question types with more response fields
357 * should override this method and set the ->responses field to an
358 * associative array of responses.
359 * @return bool Indicates success or failure.
360 * @param object $question The question object for the question including any
361 * question type specific information.
362 * @param object $state The saved state to load the session for. This
363 * object should be updated to include the question
364 * type specific session information and responses
365 * (it is passed by reference).
366 */
367 function restore_session_and_responses(&$question, &$state) {
368 // The default implementation does nothing (successfully)
369 return true;
370 }
371
372 /**
373 * Saves the session data and responses for the given question and state
374 *
375 * This function saves the question type specific session data from the
376 * state object to the database. In particular for most question types it saves the
377 * responses from the ->responses member of the state object. The question type
4f48fb42 378 * non-specific data for the state has already been saved in the question_states
516cf3eb 379 * table and the state object contains the corresponding id and
380 * sequence number which may be used to index a question type specific table.
381 *
382 * Question types with only a single form field for the student's response
383 * which is contained in ->responses[''] will not have to save this response,
4f48fb42 384 * it will already have been saved to the answer field of the question_states table.
516cf3eb 385 * Question types with more response fields should override this method and save
386 * the responses in their own database tables.
387 * @return bool Indicates success or failure.
388 * @param object $question The question object for the question including
389 * the question type specific information.
390 * @param object $state The state for which the question type specific
391 * data and responses should be saved.
392 */
393 function save_session_and_responses(&$question, &$state) {
394 // The default implementation does nothing (successfully)
395 return true;
396 }
397
398 /**
399 * Returns an array of values which will give full marks if graded as
400 * the $state->responses field
401 *
402 * The correct answer to the question in the given state, or an example of
403 * a correct answer if there are many, is returned. This is used by some question
404 * types in the {@link grade_responses()} function but it is also used by the
405 * question preview screen to fill in correct responses.
406 * @return mixed A response array giving the responses corresponding
407 * to the (or a) correct answer to the question. If there is
408 * no correct answer that scores 100% then null is returned.
409 * @param object $question The question for which the correct answer is to
410 * be retrieved. Question type specific information is
411 * available.
412 * @param object $state The state of the question, for which a correct answer is
413 * needed. Question type specific information is included.
414 */
415 function get_correct_responses(&$question, &$state) {
416 /* The default implementation returns the response for the first answer
417 that gives full marks. */
4eda4eec 418 if ($question->options->answers) {
419 foreach ($question->options->answers as $answer) {
420 if (((int) $answer->fraction) === 1) {
421 return array('' => $answer->answer);
422 }
516cf3eb 423 }
424 }
425 return null;
426 }
427
428 /**
429 * Return an array of values with the texts for all possible responses stored
430 * for the question
431 *
432 * All answers are found and their text values isolated
433 * @return object A mixed object
434 * ->id question id. Needed to manage random questions:
435 * it's the id of the actual question presented to user in a given attempt
436 * ->responses An array of values giving the responses corresponding
437 * to all answers to the question. Answer ids are used as keys.
438 * The text and partial credit are the object components
439 * @param object $question The question for which the answers are to
440 * be retrieved. Question type specific information is
441 * available.
442 */
443 // ULPGC ecastro
444 function get_all_responses(&$question, &$state) {
7baff8aa 445 if (isset($question->options->answers) && is_array($question->options->answers)) {
446 $answers = array();
516cf3eb 447 foreach ($question->options->answers as $aid=>$answer) {
7baff8aa 448 $r = new stdClass;
516cf3eb 449 $r->answer = $answer->answer;
450 $r->credit = $answer->fraction;
451 $answers[$aid] = $r;
452 }
7baff8aa 453 $result = new stdClass;
454 $result->id = $question->id;
455 $result->responses = $answers;
456 return $result;
516cf3eb 457 } else {
7baff8aa 458 return null;
516cf3eb 459 }
516cf3eb 460 }
461
462 /**
463 * Return the actual response to the question in a given state
464 * for the question
465 *
466 * @return mixed An array containing the response or reponses (multiple answer, match)
467 * given by the user in a particular attempt.
468 * @param object $question The question for which the correct answer is to
469 * be retrieved. Question type specific information is
470 * available.
471 * @param object $state The state object that corresponds to the question,
472 * for which a correct answer is needed. Question
473 * type specific information is included.
474 */
475 // ULPGC ecastro
8cc274de 476 function get_actual_response($question, $state) {
477 // change length to truncate responses here if you want
478 $lmax = 40;
479 if (!empty($state->responses)) {
480 $responses[] = (strlen($state->responses['']) > $lmax) ?
481 substr($state->responses[''], 0, $lmax).'...' : $state->responses[''];
482 } else {
483 $responses[] = '';
484 }
485 return $responses;
516cf3eb 486 }
487
488 // ULPGC ecastro
489 function get_fractional_grade(&$question, &$state) {
490 $maxgrade = $question->maxgrade;
491 $grade = $state->grade;
492 if ($maxgrade) {
493 return (float)($grade/$maxgrade);
494 } else {
495 return (float)$grade;
496 }
497 }
498
499
500 /**
501 * Checks if the response given is correct and returns the id
502 *
503 * @return int The ide number for the stored answer that matches the response
504 * given by the user in a particular attempt.
505 * @param object $question The question for which the correct answer is to
506 * be retrieved. Question type specific information is
507 * available.
508 * @param object $state The state object that corresponds to the question,
509 * for which a correct answer is needed. Question
510 * type specific information is included.
511 */
512 // ULPGC ecastro
513 function check_response(&$question, &$state){
514 return false;
515 }
516
517 /**
518 * Prints the question including the number, grading details, content,
519 * feedback and interactions
520 *
521 * This function prints the question including the question number,
522 * grading details, content for the question, any feedback for the previously
523 * submitted responses and the interactions. The default implementation calls
524 * various other methods to print each of these parts and most question types
525 * will just override those methods.
516cf3eb 526 * @param object $question The question to be rendered. Question type
527 * specific information is included. The
528 * maximum possible grade is in ->maxgrade. The name
529 * prefix for any named elements is in ->name_prefix.
530 * @param object $state The state to render the question in. The grading
531 * information is in ->grade, ->raw_grade and
532 * ->penalty. The current responses are in
533 * ->responses. This is an associative array (or the
534 * empty string or null in the case of no responses
535 * submitted). The last graded state is in
536 * ->last_graded (hence the most recently graded
537 * responses are in ->last_graded->responses). The
538 * question type specific information is also
539 * included.
540 * @param integer $number The number for this question.
541 * @param object $cmoptions
542 * @param object $options An object describing the rendering options.
543 */
544 function print_question(&$question, &$state, $number, $cmoptions, $options) {
545 /* The default implementation should work for most question types
546 provided the member functions it calls are overridden where required.
37a12367 547 The layout is determined by the template question.html */
271ffe3f 548
516cf3eb 549 global $CFG;
1b8a7434 550 $isgraded = question_state_is_graded($state->last_graded);
516cf3eb 551
647bcd41 552 // If this question is being shown in the context of a quiz
553 // get the context so we can determine whether some extra links
271ffe3f 554 // should be shown. (Don't show these links during question preview.)
d758f1e2 555 $cm = get_coursemodule_from_instance('quiz', $cmoptions->id);
647bcd41 556 if (!empty($cm->id)) {
557 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
ec6adca0 558 } else if (!empty($cm->course)) {
559 $context = get_context_instance(CONTEXT_COURSE, $cm->course);
647bcd41 560 } else {
ec6adca0 561 $context = get_context_instance(CONTEXT_SYSTEM, SITEID);
647bcd41 562 }
271ffe3f 563
516cf3eb 564 // For editing teachers print a link to an editing popup window
565 $editlink = '';
ec6adca0 566 if ($context && has_capability('moodle/question:manage', $context)) {
516cf3eb 567 $stredit = get_string('edit');
0fd5feef 568 $linktext = '<img src="'.$CFG->pixpath.'/t/edit.gif" alt="'.$stredit.'" />';
ee6c9355 569 $editlink = link_to_popup_window('/question/question.php?inpopup=1&amp;id='.$question->id, 'editquestion', $linktext, 450, 550, $stredit, '', true);
516cf3eb 570 }
571
a4514d91 572 $generalfeedback = '';
573 if ($isgraded && $options->generalfeedback) {
574 $generalfeedback = $this->format_text($question->generalfeedback,
1b8a7434 575 $question->questiontextformat, $cmoptions);
576 }
577
516cf3eb 578 $grade = '';
579 if ($question->maxgrade and $options->scores) {
6b11a0e8 580 if ($cmoptions->optionflags & QUESTION_ADAPTIVE) {
1b8a7434 581 $grade = !$isgraded ? '--/' : round($state->last_graded->grade, $cmoptions->decimalpoints).'/';
516cf3eb 582 }
583 $grade .= $question->maxgrade;
584 }
271ffe3f 585
3e3e5a40 586 $comment = stripslashes($state->manualcomment);
b6e907a2 587 $commentlink = '';
271ffe3f 588
647bcd41 589 if (isset($options->questioncommentlink) && $context && has_capability('mod/quiz:grade', $context)) {
b6e907a2 590 $strcomment = get_string('commentorgrade', 'quiz');
097bd3f5 591 $commentlink = '<div class="commentlink">'.link_to_popup_window ($options->questioncommentlink.'?attempt='.$state->attempt.'&amp;question='.$question->id,
592 'commentquestion', $strcomment, 450, 650, $strcomment, 'none', true).'</div>';
b6e907a2 593 }
516cf3eb 594
fe9b5cfd 595 $history = $this->history($question, $state, $number, $cmoptions, $options);
596
aaae75b0 597 include "$CFG->dirroot/question/type/question.html";
fe9b5cfd 598 }
599
600 /*
601 * Print history of responses
602 *
603 * Used by print_question()
604 */
605 function history($question, $state, $number, $cmoptions, $options) {
516cf3eb 606 $history = '';
607 if(isset($options->history) and $options->history) {
608 if ($options->history == 'all') {
609 // show all states
755bddf1 610 $states = get_records_select('question_states', "attempt = '$state->attempt' AND question = '$question->id' AND event > '0'", 'seq_number ASC');
516cf3eb 611 } else {
612 // show only graded states
755bddf1 613 $states = get_records_select('question_states', "attempt = '$state->attempt' AND question = '$question->id' AND event IN (".QUESTION_EVENTGRADE.','.QUESTION_EVENTCLOSEANDGRADE.")", 'seq_number ASC');
516cf3eb 614 }
615 if (count($states) > 1) {
616 $strreviewquestion = get_string('reviewresponse', 'quiz');
1b8a7434 617 $table = new stdClass;
516cf3eb 618 $table->width = '100%';
d57cf4c1 619 if ($options->scores) {
620 $table->head = array (
621 get_string('numberabbr', 'quiz'),
622 get_string('action', 'quiz'),
623 get_string('response', 'quiz'),
624 get_string('time'),
625 get_string('score', 'quiz'),
626 //get_string('penalty', 'quiz'),
627 get_string('grade', 'quiz'),
628 );
629 } else {
630 $table->head = array (
631 get_string('numberabbr', 'quiz'),
632 get_string('action', 'quiz'),
633 get_string('response', 'quiz'),
634 get_string('time'),
635 );
3925a20a 636 }
271ffe3f 637
516cf3eb 638 foreach ($states as $st) {
0a5b58af 639 $st->responses[''] = $st->answer;
640 $this->restore_session_and_responses($question, $st);
516cf3eb 641 $b = ($state->id == $st->id) ? '<b>' : '';
642 $be = ($state->id == $st->id) ? '</b>' : '';
fe9b5cfd 643 if ($state->id == $st->id) {
644 $link = '<b>'.$st->seq_number.'</b>';
645 } else {
646 if(isset($options->questionreviewlink)) {
647 $link = link_to_popup_window ($options->questionreviewlink.'?state='.$st->id.'&amp;number='.$number,
648 'reviewquestion', $st->seq_number, 450, 650, $strreviewquestion, 'none', true);
649 } else {
650 $link = $st->seq_number;
651 }
652 }
d57cf4c1 653 if ($options->scores) {
654 $table->data[] = array (
655 $link,
656 $b.get_string('event'.$st->event, 'quiz').$be,
657 $b.$this->response_summary($question, $st).$be,
658 $b.userdate($st->timestamp, get_string('timestr', 'quiz')).$be,
659 $b.round($st->raw_grade, $cmoptions->decimalpoints).$be,
660 //$b.round($st->penalty, $cmoptions->decimalpoints).$be,
661 $b.round($st->grade, $cmoptions->decimalpoints).$be
662 );
663 } else {
664 $table->data[] = array (
665 $link,
666 $b.get_string('event'.$st->event, 'quiz').$be,
667 $b.$this->response_summary($question, $st).$be,
668 $b.userdate($st->timestamp, get_string('timestr', 'quiz')).$be,
669 );
670 }
516cf3eb 671 }
672 $history = make_table($table);
673 }
674 }
fe9b5cfd 675 return $history;
516cf3eb 676 }
677
678
679 /**
680 * Prints the score obtained and maximum score available plus any penalty
681 * information
682 *
683 * This function prints a summary of the scoring in the most recently
684 * graded state (the question may not have been submitted for marking at
685 * the current state). The default implementation should be suitable for most
686 * question types.
687 * @param object $question The question for which the grading details are
688 * to be rendered. Question type specific information
689 * is included. The maximum possible grade is in
690 * ->maxgrade.
691 * @param object $state The state. In particular the grading information
692 * is in ->grade, ->raw_grade and ->penalty.
693 * @param object $cmoptions
694 * @param object $options An object describing the rendering options.
695 */
696 function print_question_grading_details(&$question, &$state, $cmoptions, $options) {
697 /* The default implementation prints the number of marks if no attempt
698 has been made. Otherwise it displays the grade obtained out of the
699 maximum grade available and a warning if a penalty was applied for the
700 attempt and displays the overall grade obtained counting all previous
701 responses (and penalties) */
702
f30bbcaf 703 if (QUESTION_EVENTDUPLICATE == $state->event) {
516cf3eb 704 echo ' ';
705 print_string('duplicateresponse', 'quiz');
706 }
707 if (!empty($question->maxgrade) && $options->scores) {
f30bbcaf 708 if (question_state_is_graded($state->last_graded)) {
516cf3eb 709 // Display the grading details from the last graded state
1b8a7434 710 $grade = new stdClass;
516cf3eb 711 $grade->cur = round($state->last_graded->grade, $cmoptions->decimalpoints);
712 $grade->max = $question->maxgrade;
713 $grade->raw = round($state->last_graded->raw_grade, $cmoptions->decimalpoints);
714
715 // let student know wether the answer was correct
716 echo '<div class="correctness ';
717 if ($state->last_graded->raw_grade >= $question->maxgrade/1.01) { // We divide by 1.01 so that rounding errors dont matter.
718 echo ' correct">';
719 print_string('correct', 'quiz');
720 } else if ($state->last_graded->raw_grade > 0) {
721 echo ' partiallycorrect">';
722 print_string('partiallycorrect', 'quiz');
723 } else {
724 echo ' incorrect">';
725 print_string('incorrect', 'quiz');
726 }
727 echo '</div>';
728
729 echo '<div class="gradingdetails">';
730 // print grade for this submission
731 print_string('gradingdetails', 'quiz', $grade);
732 if ($cmoptions->penaltyscheme) {
733 // print details of grade adjustment due to penalties
734 if ($state->last_graded->raw_grade > $state->last_graded->grade){
2962ad61 735 echo ' ';
516cf3eb 736 print_string('gradingdetailsadjustment', 'quiz', $grade);
737 }
738 // print info about new penalty
739 // penalty is relevant only if the answer is not correct and further attempts are possible
f30bbcaf 740 if (($state->last_graded->raw_grade < $question->maxgrade) and (QUESTION_EVENTCLOSEANDGRADE !== $state->event)) {
516cf3eb 741 if ('' !== $state->last_graded->penalty && ((float)$state->last_graded->penalty) > 0.0) {
742 // A penalty was applied so display it
2962ad61 743 echo ' ';
516cf3eb 744 print_string('gradingdetailspenalty', 'quiz', $state->last_graded->penalty);
745 } else {
746 /* No penalty was applied even though the answer was
747 not correct (eg. a syntax error) so tell the student
748 that they were not penalised for the attempt */
2962ad61 749 echo ' ';
516cf3eb 750 print_string('gradingdetailszeropenalty', 'quiz');
751 }
752 }
753 }
754 echo '</div>';
755 }
756 }
757 }
758
759 /**
760 * Prints the main content of the question including any interactions
761 *
762 * This function prints the main content of the question including the
763 * interactions for the question in the state given. The last graded responses
764 * are printed or indicated and the current responses are selected or filled in.
765 * Any names (eg. for any form elements) are prefixed with $question->name_prefix.
766 * This method is called from the print_question method.
767 * @param object $question The question to be rendered. Question type
768 * specific information is included. The name
769 * prefix for any named elements is in ->name_prefix.
770 * @param object $state The state to render the question in. The grading
771 * information is in ->grade, ->raw_grade and
772 * ->penalty. The current responses are in
773 * ->responses. This is an associative array (or the
774 * empty string or null in the case of no responses
775 * submitted). The last graded state is in
776 * ->last_graded (hence the most recently graded
777 * responses are in ->last_graded->responses). The
778 * question type specific information is also
779 * included.
780 * The state is passed by reference because some adaptive
781 * questions may want to update it during rendering
782 * @param object $cmoptions
783 * @param object $options An object describing the rendering options.
784 */
785 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
786 /* This default implementation prints an error and must be overridden
787 by all question type implementations, unless the default implementation
788 of print_question has been overridden. */
789
790 notify('Error: Question formulation and input controls has not'
791 .' been implemented for question type '.$this->name());
792 }
793
794 /**
795 * Prints the submit button(s) for the question in the given state
796 *
797 * This function prints the submit button(s) for the question in the
798 * given state. The name of any button created will be prefixed with the
799 * unique prefix for the question in $question->name_prefix. The suffix
4dca7e51 800 * 'submit' is reserved for the single question submit button and the suffix
516cf3eb 801 * 'validate' is reserved for the single question validate button (for
802 * question types which support it). Other suffixes will result in a response
803 * of that name in $state->responses which the printing and grading methods
804 * can then use.
805 * @param object $question The question for which the submit button(s) are to
806 * be rendered. Question type specific information is
807 * included. The name prefix for any
808 * named elements is in ->name_prefix.
809 * @param object $state The state to render the buttons for. The
810 * question type specific information is also
811 * included.
812 * @param object $cmoptions
813 * @param object $options An object describing the rendering options.
814 */
815 function print_question_submit_buttons(&$question, &$state, $cmoptions, $options) {
816 /* The default implementation should be suitable for most question
817 types. It prints a mark button in the case where individual marking is
818 allowed. */
819
6b11a0e8 820 if (($cmoptions->optionflags & QUESTION_ADAPTIVE) and !$options->readonly) {
516cf3eb 821 echo '<input type="submit" name="';
822 echo $question->name_prefix;
4dca7e51 823 echo 'submit" value="';
516cf3eb 824 print_string('mark', 'quiz');
5bc57211 825 echo '" class="submit btn"';
826 echo ' />';
516cf3eb 827 }
828 }
829
830
831 /**
832 * Return a summary of the student response
833 *
834 * This function returns a short string of no more than a given length that
835 * summarizes the student's response in the given $state. This is used for
836 * example in the response history table
837 * @return string The summary of the student response
271ffe3f 838 * @param object $question
516cf3eb 839 * @param object $state The state whose responses are to be summarized
840 * @param int $length The maximum length of the returned string
841 */
0a5b58af 842 function response_summary($question, $state, $length=80) {
516cf3eb 843 // This should almost certainly be overridden
879caa51 844 $responses = $this->get_actual_response($question, $state);
845 if (empty($responses) || !is_array($responses)) {
846 $responses = array();
847 }
848 if (is_array($responses)) {
849 $responses = implode(',', $responses);
850 }
851 return substr($responses, 0, $length);
516cf3eb 852 }
853
854 /**
855 * Renders the question for printing and returns the LaTeX source produced
856 *
857 * This function should render the question suitable for a printed problem
858 * or solution sheet in LaTeX and return the rendered output.
859 * @return string The LaTeX output.
860 * @param object $question The question to be rendered. Question type
861 * specific information is included.
862 * @param object $state The state to render the question in. The
863 * question type specific information is also
864 * included.
865 * @param object $cmoptions
866 * @param string $type Indicates if the question or the solution is to be
867 * rendered with the values 'question' and
868 * 'solution'.
869 */
870 function get_texsource(&$question, &$state, $cmoptions, $type) {
871 // The default implementation simply returns a string stating that
872 // the question is only available online.
873
874 return get_string('onlineonly', 'texsheet');
875 }
876
877 /**
878 * Compares two question states for equivalence of the student's responses
879 *
880 * The responses for the two states must be examined to see if they represent
881 * equivalent answers to the question by the student. This method will be
882 * invoked for each of the previous states of the question before grading
883 * occurs. If the student is found to have already attempted the question
884 * with equivalent responses then the attempt at the question is ignored;
885 * grading does not occur and the state does not change. Thus they are not
886 * penalized for this case.
887 * @return boolean
888 * @param object $question The question for which the states are to be
889 * compared. Question type specific information is
890 * included.
891 * @param object $state The state of the question. The responses are in
ac249da5 892 * ->responses. This is the only field of $state
893 * that it is safe to use.
516cf3eb 894 * @param object $teststate The state whose responses are to be
895 * compared. The state will be of the same age or
271ffe3f 896 * older than $state. If possible, the method should
bb080d20 897 * only use the field $teststate->responses, however
898 * any field that is set up by restore_session_and_responses
899 * can be used.
516cf3eb 900 */
901 function compare_responses(&$question, $state, $teststate) {
902 // The default implementation performs a comparison of the response
903 // arrays. The ordering of the arrays does not matter.
904 // Question types may wish to override this (eg. to ignore trailing
905 // white space or to make "7.0" and "7" compare equal).
271ffe3f 906
c82f76d0 907 return $state->responses === $teststate->responses;
516cf3eb 908 }
909
37a12367 910 /**
911 * Checks whether a response matches a given answer
912 *
913 * This method only applies to questions that use teacher-defined answers
914 *
915 * @return boolean
916 */
917 function test_response(&$question, &$state, $answer) {
918 $response = isset($state->responses['']) ? $state->responses[''] : '';
919 return ($response == $answer->answer);
920 }
921
516cf3eb 922 /**
923 * Performs response processing and grading
924 *
925 * This function performs response processing and grading and updates
926 * the state accordingly.
927 * @return boolean Indicates success or failure.
928 * @param object $question The question to be graded. Question type
929 * specific information is included.
930 * @param object $state The state of the question to grade. The current
931 * responses are in ->responses. The last graded state
932 * is in ->last_graded (hence the most recently graded
933 * responses are in ->last_graded->responses). The
934 * question type specific information is also
935 * included. The ->raw_grade and ->penalty fields
936 * must be updated. The method is able to
937 * close the question session (preventing any further
938 * attempts at this question) by setting
f30bbcaf 939 * $state->event to QUESTION_EVENTCLOSEANDGRADE
516cf3eb 940 * @param object $cmoptions
941 */
942 function grade_responses(&$question, &$state, $cmoptions) {
5a14d563 943 // The default implementation uses the test_response method to
944 // compare what the student entered against each of the possible
271ffe3f 945 // answers stored in the question, and uses the grade from the
5a14d563 946 // first one that matches. It also sets the marks and penalty.
947 // This should be good enought for most simple question types.
516cf3eb 948
5a14d563 949 $state->raw_grade = 0;
516cf3eb 950 foreach($question->options->answers as $answer) {
5a14d563 951 if($this->test_response($question, $state, $answer)) {
952 $state->raw_grade = $answer->fraction;
516cf3eb 953 break;
954 }
955 }
5a14d563 956
957 // Make sure we don't assign negative or too high marks.
958 $state->raw_grade = min(max((float) $state->raw_grade,
959 0.0), 1.0) * $question->maxgrade;
271ffe3f 960
5a14d563 961 // Update the penalty.
962 $state->penalty = $question->penalty * $question->maxgrade;
516cf3eb 963
f30bbcaf 964 // mark the state as graded
965 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
966
516cf3eb 967 return true;
968 }
969
970
971 /**
972 * Includes configuration settings for the question type on the quiz admin
973 * page
974 *
7518b645 975 * TODO: It makes no sense any longer to do the admin for question types
976 * from the quiz admin page. This should be changed.
516cf3eb 977 * Returns an array of objects describing the options for the question type
978 * to be included on the quiz module admin page.
979 * Configuration options can be included by setting the following fields in
980 * the object:
981 * ->name The name of the option within this question type.
982 * The full option name will be constructed as
983 * "quiz_{$this->name()}_$name", the human readable name
984 * will be displayed with get_string($name, 'quiz').
985 * ->code The code to display the form element, help button, etc.
986 * i.e. the content for the central table cell. Be sure
987 * to name the element "quiz_{$this->name()}_$name" and
988 * set the value to $CFG->{"quiz_{$this->name()}_$name"}.
989 * ->help Name of the string from the quiz module language file
990 * to be used for the help message in the third column of
991 * the table. An empty string (or the field not set)
992 * means to leave the box empty.
993 * Links to custom settings pages can be included by setting the following
994 * fields in the object:
995 * ->name The name of the link text string.
996 * get_string($name, 'quiz') will be called.
997 * ->link The filename part of the URL for the link. The full URL
998 * is contructed as
aaae75b0 999 * "$CFG->wwwroot/question/type/{$this->name()}/$link?sesskey=$sesskey"
516cf3eb 1000 * [but with the relavant calls to the s and rawurlencode
1001 * functions] where $sesskey is the sesskey for the user.
1002 * @return array Array of objects describing the configuration options to
1003 * be included on the quiz module admin page.
1004 */
1005 function get_config_options() {
1006 // No options by default
1007
1008 return false;
1009 }
1010
1011 /**
dc1f00de 1012 * Returns true if the editing wizard is finished, false otherwise.
1013 *
1014 * The default implementation returns true, which is suitable for all question-
516cf3eb 1015 * types that only use one editing form. This function is used in
1016 * question.php to decide whether we can regrade any states of the edited
1017 * question and redirect to edit.php.
1018 *
1019 * The dataset dependent question-type, which is extended by the calculated
1020 * question-type, overwrites this method because it uses multiple pages (i.e.
1021 * a wizard) to set up the question and associated datasets.
1022 *
1023 * @param object $form The data submitted by the previous page.
1024 *
1025 * @return boolean Whether the wizard's last page was submitted or not.
1026 */
1027 function finished_edit_wizard(&$form) {
1028 //In the default case there is only one edit page.
1029 return true;
1030 }
1031
dc1f00de 1032 /**
1033 * Prints a table of course modules in which the question is used
1034 *
7518b645 1035 * TODO: This should be made quiz-independent
1036 *
dc1f00de 1037 * This function is used near the end of the question edit forms in all question types
1038 * It prints the table of quizzes in which the question is used
1039 * containing checkboxes to allow the teacher to replace the old question version
1040 *
1041 * @param object $question
1042 * @param object $course
1043 * @param integer $cmid optional The id of the course module currently being edited
1044 */
1045 function print_replacement_options($question, $course, $cmid='0') {
516cf3eb 1046
1047 // Disable until the versioning code has been fixed
20808266 1048 if (true) {
1049 return;
1050 }
271ffe3f 1051
516cf3eb 1052 // no need to display replacement options if the question is new
1053 if(empty($question->id)) {
1054 return true;
1055 }
1056
1057 // get quizzes using the question (using the question_instances table)
1058 $quizlist = array();
1059 if(!$instances = get_records('quiz_question_instances', 'question', $question->id)) {
1060 $instances = array();
1061 }
1062 foreach($instances as $instance) {
1063 $quizlist[$instance->quiz] = $instance->quiz;
1064 }
1065 $quizlist = implode(',', $quizlist);
1066 if(empty($quizlist) or !$quizzes = get_records_list('quiz', 'id', $quizlist)) {
1067 $quizzes = array();
1068 }
1069
1070 // do the printing
1071 if(count($quizzes) > 0) {
1072 // print the table
1073 $strquizname = get_string('modulename', 'quiz');
1074 $strdoreplace = get_string('replace', 'quiz');
1075 $straffectedstudents = get_string('affectedstudents', 'quiz', $course->students);
1076 echo "<tr valign=\"top\">\n";
1077 echo "<td align=\"right\"><b>".get_string("replacementoptions", "quiz").":</b></td>\n";
1078 echo "<td align=\"left\">\n";
1079 echo "<table cellpadding=\"5\" align=\"left\" class=\"generalbox\" width=\"100%\">\n";
1080 echo "<tr>\n";
077f3814 1081 echo "<th align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"generaltableheader c0\" scope=\"col\">$strquizname</th>\n";
1082 echo "<th align=\"center\" valign=\"top\" nowrap=\"nowrap\" class=\"generaltableheader c0\" scope=\"col\">$strdoreplace</th>\n";
1083 echo "<th align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"generaltableheader c0\" scope=\"col\">$straffectedstudents</th>\n";
516cf3eb 1084 echo "</tr>\n";
1085 foreach($quizzes as $quiz) {
1086 // work out whethere it should be checked by default
1087 $checked = '';
dc1f00de 1088 if((int)$cmid === (int)$quiz->id
516cf3eb 1089 or empty($quiz->usercount)) {
1090 $checked = "checked=\"checked\"";
1091 }
1092
1093 // find how many different students have already attempted this quiz
1094 $students = array();
1095 if($attempts = get_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'")) {
1096 foreach($attempts as $attempt) {
4f48fb42 1097 if (record_exists('question_states', 'attempt', $attempt->uniqueid, 'question', $question->id, 'originalquestion', 0)) {
516cf3eb 1098 $students[$attempt->userid] = 1;
1099 }
1100 }
1101 }
1102 $studentcount = count($students);
1103
1104 $strstudents = $studentcount === 1 ? $course->student : $course->students;
1105 echo "<tr>\n";
1106 echo "<td align=\"left\" class=\"generaltablecell c0\">".format_string($quiz->name)."</td>\n";
1107 echo "<td align=\"center\" class=\"generaltablecell c0\"><input name=\"q{$quiz->id}replace\" type=\"checkbox\" ".$checked." /></td>\n";
1108 echo "<td align=\"left\" class=\"generaltablecell c0\">".(($studentcount) ? $studentcount.' '.$strstudents : '-')."</td>\n";
1109 echo "</tr>\n";
1110 }
1111 echo "</table>\n";
1112 }
1113 echo "</td></tr>\n";
1114 }
1115
1b8a7434 1116 /**
1117 * Print the start of the question editing form, including the question category,
a4514d91 1118 * questionname, questiontext, image, defaultgrade, penalty and generalfeedback fields.
271ffe3f 1119 *
1b8a7434 1120 * Three of the fields, image, defaultgrade, penalty, are optional, and
271ffe3f 1121 * can be removed from the from using the $hidefields argument.
1122 *
1b8a7434 1123 * @param object $question The question object that the form we are printing is for.
1124 * @param array $err Array of optional error messages to display by each field.
1125 * Used when the form is being redisplayed after validation failed.
1126 * @param object $course The course object for the course this question belongs to.
1127 * @param boolean $usehtmleditor Whether the html editor should be used.
1128 * @param array $hidefields An array which may contain the strings,
1129 * 'image', 'defaultgrade' or 'penalty' to remove the corresponding field.
1130 */
1131 function print_question_form_start($question, $err, $course, $usehtmleditor, $hidefields = array()) {
1132 global $CFG;
1133
1134 // If you edit this function, you also need to edit random/editquestion.html.
271ffe3f 1135
1b8a7434 1136 if (!in_array('image', $hidefields)) {
1137 make_upload_directory("$course->id"); // Just in case
1138 $coursefiles = get_directory_list("$CFG->dataroot/$course->id", $CFG->moddata);
1139 foreach ($coursefiles as $filename) {
1140 if (mimeinfo("icon", $filename) == "image.gif") {
1141 $images["$filename"] = $filename;
1142 }
1143 }
516cf3eb 1144 }
271ffe3f 1145
1b8a7434 1146 include('editquestionstart.html');
1147 }
271ffe3f 1148
1b8a7434 1149 /**
1150 * Print the end of the question editing form, including the submit, copy,
1151 * and cancel button, and the standard hidden fields like the sesskey and
1152 * the question type.
271ffe3f 1153 *
1b8a7434 1154 * @param object $question The question object that the form we are printing is for.
1155 * @param string $submitscript Extra attributes, for example 'onsubmit="myfunction"',
1156 * that is added to the HTML of the submit button.
1157 * @param string $hiddenfields Extra hidden fields (actually any HTML)
1158 * to be added at the end of the form.
1159 */
1160 function print_question_form_end($question, $submitscript = '', $hiddenfields = '') {
1161 global $USER;
271ffe3f 1162
1b8a7434 1163 // If you edit this function, you also need to edit random/editquestion.html.
1164
1165 include('editquestionend.html');
1166 }
271ffe3f 1167
1b8a7434 1168 /**
1169 * Call format_text from weblib.php with the options appropriate to question types.
271ffe3f 1170 *
1b8a7434 1171 * @param string $text the text to format.
1172 * @param integer $text the type of text. Normally $question->questiontextformat.
1173 * @param object $cmoptions the context the string is being displayed in. Only $cmoptions->course is used.
1174 * @return string the formatted text.
1175 */
1176 function format_text($text, $textformat, $cmoptions) {
1177 $formatoptions = new stdClass;
1178 $formatoptions->noclean = true;
1179 $formatoptions->para = false;
1180 return format_text($text, $textformat, $formatoptions, $cmoptions->course);
516cf3eb 1181 }
271ffe3f 1182
c5d94c41 1183/// BACKUP FUNCTIONS ////////////////////////////
1184
1185 /*
1186 * Backup the data in the question
1187 *
1188 * This is used in question/backuplib.php
1189 */
1190 function backup($bf,$preferences,$question,$level=6) {
1191 // The default type has nothing to back up
1192 return true;
1193 }
1194
315559d3 1195/// RESTORE FUNCTIONS /////////////////
1196
1197 /*
1198 * Restores the data in the question
1199 *
1200 * This is used in question/restorelib.php
1201 */
1202 function restore($old_question_id,$new_question_id,$info,$restore) {
1203 // The default question type has nothing to restore
1204 return true;
1205 }
1206
1207 function restore_map($old_question_id,$new_question_id,$info,$restore) {
1208 // There is nothing to decode
1209 return true;
1210 }
1211
1212 function restore_recode_answer($state, $restore) {
1213 // There is nothing to decode
ba8f7ff0 1214 return $state->answer;
271ffe3f 1215 }
315559d3 1216
1217 //This function restores the question_rqp_states
1218 function restore_state($state_id,$info,$restore) {
1219 // The default question type does not keep its own state information
1220 return true;
1221 }
516cf3eb 1222
1223}
1224
96ed722b 1225?>