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