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