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