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