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