|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 00052 class question_usage_by_activity { 00058 protected $id = null; 00059 00064 protected $preferredbehaviour = null; 00065 00067 protected $context; 00068 00070 protected $owningcomponent; 00071 00073 protected $questionattempts = array(); 00074 00076 protected $observer; 00077 00087 public function __construct($component, $context) { 00088 $this->owningcomponent = $component; 00089 $this->context = $context; 00090 $this->observer = new question_usage_null_observer(); 00091 } 00092 00097 public function set_preferred_behaviour($behaviour) { 00098 $this->preferredbehaviour = $behaviour; 00099 $this->observer->notify_modified(); 00100 } 00101 00103 public function get_preferred_behaviour() { 00104 return $this->preferredbehaviour; 00105 } 00106 00108 public function get_owning_context() { 00109 return $this->context; 00110 } 00111 00113 public function get_owning_component() { 00114 return $this->owningcomponent; 00115 } 00116 00120 public function get_id() { 00121 if (is_null($this->id)) { 00122 $this->id = random_string(10); 00123 } 00124 return $this->id; 00125 } 00126 00132 public function set_id_from_database($id) { 00133 $this->id = $id; 00134 foreach ($this->questionattempts as $qa) { 00135 $qa->set_usage_id($id); 00136 } 00137 } 00138 00140 public function get_observer() { 00141 return $this->observer; 00142 } 00143 00149 public function set_observer($observer) { 00150 $this->observer = $observer; 00151 foreach ($this->questionattempts as $qa) { 00152 $qa->set_observer($observer); 00153 } 00154 } 00155 00167 public function add_question(question_definition $question, $maxmark = null) { 00168 $qa = new question_attempt($question, $this->get_id(), $this->observer, $maxmark); 00169 if (count($this->questionattempts) == 0) { 00170 $this->questionattempts[1] = $qa; 00171 } else { 00172 $this->questionattempts[] = $qa; 00173 } 00174 $qa->set_slot(end(array_keys($this->questionattempts))); 00175 $this->observer->notify_attempt_added($qa); 00176 return $qa->get_slot(); 00177 } 00178 00184 public function get_question($slot) { 00185 return $this->get_question_attempt($slot)->get_question(); 00186 } 00187 00189 public function get_slots() { 00190 return array_keys($this->questionattempts); 00191 } 00192 00194 public function get_first_question_number() { 00195 reset($this->questionattempts); 00196 return key($this->questionattempts); 00197 } 00198 00200 public function question_count() { 00201 return count($this->questionattempts); 00202 } 00203 00212 public function get_attempt_iterator() { 00213 return new question_attempt_iterator($this); 00214 } 00215 00222 protected function check_slot($slot) { 00223 if (!array_key_exists($slot, $this->questionattempts)) { 00224 throw new coding_exception('There is no question_attempt number ' . $slot . 00225 ' in this attempt.'); 00226 } 00227 } 00228 00237 public function get_question_attempt($slot) { 00238 $this->check_slot($slot); 00239 return $this->questionattempts[$slot]; 00240 } 00241 00247 public function get_question_state($slot) { 00248 return $this->get_question_attempt($slot)->get_state(); 00249 } 00250 00257 public function get_question_state_string($slot, $showcorrectness) { 00258 return $this->get_question_attempt($slot)->get_state_string($showcorrectness); 00259 } 00260 00267 public function get_question_state_class($slot, $showcorrectness) { 00268 return $this->get_question_attempt($slot)->get_state_class($showcorrectness); 00269 } 00270 00276 public function get_question_action_time($slot) { 00277 return $this->get_question_attempt($slot)->get_last_action_time(); 00278 } 00279 00286 public function get_question_fraction($slot) { 00287 return $this->get_question_attempt($slot)->get_fraction(); 00288 } 00289 00296 public function get_question_mark($slot) { 00297 return $this->get_question_attempt($slot)->get_mark(); 00298 } 00299 00305 public function get_question_max_mark($slot) { 00306 return $this->get_question_attempt($slot)->get_max_mark(); 00307 } 00308 00315 public function get_total_mark() { 00316 $mark = 0; 00317 foreach ($this->questionattempts as $qa) { 00318 if ($qa->get_max_mark() > 0 && $qa->get_state() == question_state::$needsgrading) { 00319 return null; 00320 } 00321 $mark += $qa->get_mark(); 00322 } 00323 return $mark; 00324 } 00325 00329 public function get_question_summary($slot) { 00330 return $this->get_question_attempt($slot)->get_question_summary(); 00331 } 00332 00336 public function get_response_summary($slot) { 00337 return $this->get_question_attempt($slot)->get_response_summary(); 00338 } 00339 00343 public function get_right_answer_summary($slot) { 00344 return $this->get_question_attempt($slot)->get_right_answer_summary(); 00345 } 00346 00357 public function render_question($slot, $options, $number = null) { 00358 $options->context = $this->context; 00359 return $this->get_question_attempt($slot)->render($options, $number); 00360 } 00361 00368 public function render_question_head_html($slot) { 00369 //$options->context = $this->context; 00370 return $this->get_question_attempt($slot)->render_head_html(); 00371 } 00372 00384 public function render_question_at_step($slot, $seq, $options, $number = null) { 00385 $options->context = $this->context; 00386 return $this->get_question_attempt($slot)->render_at_step( 00387 $seq, $options, $number, $this->preferredbehaviour); 00388 } 00389 00400 public function check_file_access($slot, $options, $component, $filearea, 00401 $args, $forcedownload) { 00402 return $this->get_question_attempt($slot)->check_file_access( 00403 $options, $component, $filearea, $args, $forcedownload); 00404 } 00405 00416 public function replace_loaded_question_attempt_info($slot, $qa) { 00417 $this->check_slot($slot); 00418 $this->questionattempts[$slot] = $qa; 00419 } 00420 00428 public function get_field_prefix($slot) { 00429 return $this->get_question_attempt($slot)->get_field_prefix(); 00430 } 00431 00437 public function get_num_variants($slot) { 00438 return $this->get_question_attempt($slot)->get_question()->get_num_variants(); 00439 } 00440 00446 public function get_variant($slot) { 00447 return $this->get_question_attempt($slot)->get_variant(); 00448 } 00449 00457 public function start_question($slot, $variant = null) { 00458 if (is_null($variant)) { 00459 $variant = rand(1, $this->get_num_variants($slot)); 00460 } 00461 00462 $qa = $this->get_question_attempt($slot); 00463 $qa->start($this->preferredbehaviour, $variant); 00464 $this->observer->notify_attempt_modified($qa); 00465 } 00466 00473 public function start_all_questions(question_variant_selection_strategy $variantstrategy = null, 00474 $timestamp = null, $userid = null) { 00475 if (is_null($variantstrategy)) { 00476 $variantstrategy = new question_variant_random_strategy(); 00477 } 00478 00479 foreach ($this->questionattempts as $qa) { 00480 $qa->start($this->preferredbehaviour, $qa->select_variant($variantstrategy)); 00481 $this->observer->notify_attempt_modified($qa); 00482 } 00483 } 00484 00493 public function start_question_based_on($slot, question_attempt $oldqa) { 00494 $qa = $this->get_question_attempt($slot); 00495 $qa->start_based_on($oldqa); 00496 $this->observer->notify_attempt_modified($qa); 00497 } 00498 00512 public function process_all_actions($timestamp = null, $postdata = null) { 00513 $slots = question_attempt::get_submitted_var('slots', PARAM_SEQUENCE, $postdata); 00514 if (is_null($slots)) { 00515 $slots = $this->get_slots(); 00516 } else if (!$slots) { 00517 $slots = array(); 00518 } else { 00519 $slots = explode(',', $slots); 00520 } 00521 foreach ($slots as $slot) { 00522 if (!$this->validate_sequence_number($slot, $postdata)) { 00523 continue; 00524 } 00525 $submitteddata = $this->extract_responses($slot, $postdata); 00526 $this->process_action($slot, $submitteddata, $timestamp); 00527 } 00528 $this->update_question_flags($postdata); 00529 } 00530 00540 public function extract_responses($slot, $postdata = null) { 00541 return $this->get_question_attempt($slot)->get_submitted_data($postdata); 00542 } 00543 00549 public function process_action($slot, $submitteddata, $timestamp = null) { 00550 $qa = $this->get_question_attempt($slot); 00551 $qa->process_action($submitteddata, $timestamp); 00552 $this->observer->notify_attempt_modified($qa); 00553 } 00554 00565 public function validate_sequence_number($slot, $postdata = null) { 00566 $qa = $this->get_question_attempt($slot); 00567 $sequencecheck = $qa->get_submitted_var( 00568 $qa->get_control_field_name('sequencecheck'), PARAM_INT, $postdata); 00569 if (is_null($sequencecheck)) { 00570 return false; 00571 } else if ($sequencecheck != $qa->get_num_steps()) { 00572 throw new question_out_of_sequence_exception($this->id, $slot, $postdata); 00573 } else { 00574 return true; 00575 } 00576 } 00584 public function update_question_flags($postdata = null) { 00585 foreach ($this->questionattempts as $qa) { 00586 $flagged = $qa->get_submitted_var( 00587 $qa->get_flag_field_name(), PARAM_BOOL, $postdata); 00588 if (!is_null($flagged) && $flagged != $qa->is_flagged()) { 00589 $qa->set_flagged($flagged); 00590 } 00591 } 00592 } 00593 00601 public function get_correct_response($slot) { 00602 return $this->get_question_attempt($slot)->get_correct_response(); 00603 } 00604 00618 public function finish_question($slot, $timestamp = null) { 00619 $qa = $this->get_question_attempt($slot); 00620 $qa->finish($timestamp); 00621 $this->observer->notify_attempt_modified($qa); 00622 } 00623 00628 public function finish_all_questions($timestamp = null) { 00629 foreach ($this->questionattempts as $qa) { 00630 $qa->finish($timestamp); 00631 $this->observer->notify_attempt_modified($qa); 00632 } 00633 } 00634 00642 public function manual_grade($slot, $comment, $mark) { 00643 $qa = $this->get_question_attempt($slot); 00644 $qa->manual_grade($comment, $mark); 00645 $this->observer->notify_attempt_modified($qa); 00646 } 00647 00656 public function regrade_question($slot, $finished = false, $newmaxmark = null) { 00657 $oldqa = $this->get_question_attempt($slot); 00658 if (is_null($newmaxmark)) { 00659 $newmaxmark = $oldqa->get_max_mark(); 00660 } 00661 00662 $newqa = new question_attempt($oldqa->get_question(), $oldqa->get_usage_id(), 00663 $this->observer, $newmaxmark); 00664 $newqa->set_database_id($oldqa->get_database_id()); 00665 $newqa->set_slot($oldqa->get_slot()); 00666 $newqa->regrade($oldqa, $finished); 00667 00668 $this->questionattempts[$slot] = $newqa; 00669 $this->observer->notify_attempt_modified($newqa); 00670 } 00671 00677 public function regrade_all_questions($finished = false) { 00678 foreach ($this->questionattempts as $slot => $notused) { 00679 $this->regrade_question($slot, $finished); 00680 } 00681 } 00682 00692 public static function load_from_records($records, $qubaid) { 00693 $record = $records->current(); 00694 while ($record->qubaid != $qubaid) { 00695 $records->next(); 00696 if (!$records->valid()) { 00697 throw new coding_exception("Question usage $qubaid not found in the database."); 00698 } 00699 $record = $records->current(); 00700 } 00701 00702 $quba = new question_usage_by_activity($record->component, 00703 get_context_instance_by_id($record->contextid)); 00704 $quba->set_id_from_database($record->qubaid); 00705 $quba->set_preferred_behaviour($record->preferredbehaviour); 00706 00707 $quba->observer = new question_engine_unit_of_work($quba); 00708 00709 while ($record && $record->qubaid == $qubaid && !is_null($record->slot)) { 00710 $quba->questionattempts[$record->slot] = 00711 question_attempt::load_from_records($records, 00712 $record->questionattemptid, $quba->observer, 00713 $quba->get_preferred_behaviour()); 00714 if ($records->valid()) { 00715 $record = $records->current(); 00716 } else { 00717 $record = false; 00718 } 00719 } 00720 00721 return $quba; 00722 } 00723 } 00724 00725 00740 class question_attempt_iterator implements Iterator, ArrayAccess { 00742 protected $quba; 00744 protected $slots; 00745 00751 public function __construct(question_usage_by_activity $quba) { 00752 $this->quba = $quba; 00753 $this->slots = $quba->get_slots(); 00754 $this->rewind(); 00755 } 00756 00758 public function current() { 00759 return $this->offsetGet(current($this->slots)); 00760 } 00762 public function key() { 00763 return current($this->slots); 00764 } 00765 public function next() { 00766 next($this->slots); 00767 } 00768 public function rewind() { 00769 reset($this->slots); 00770 } 00772 public function valid() { 00773 return current($this->slots) !== false; 00774 } 00775 00777 public function offsetExists($slot) { 00778 return in_array($slot, $this->slots); 00779 } 00781 public function offsetGet($slot) { 00782 return $this->quba->get_question_attempt($slot); 00783 } 00784 public function offsetSet($slot, $value) { 00785 throw new coding_exception('You are only allowed read-only access to ' . 00786 'question_attempt::states through a question_attempt_step_iterator. Cannot set.'); 00787 } 00788 public function offsetUnset($slot) { 00789 throw new coding_exception('You are only allowed read-only access to ' . 00790 'question_attempt::states through a question_attempt_step_iterator. Cannot unset.'); 00791 } 00792 } 00793 00794 00805 interface question_usage_observer { 00807 public function notify_modified(); 00808 00813 public function notify_attempt_modified(question_attempt $qa); 00814 00819 public function notify_attempt_added(question_attempt $qa); 00820 00827 public function notify_step_added(question_attempt_step $step, question_attempt $qa, $seq); 00828 00835 public function notify_step_modified(question_attempt_step $step, question_attempt $qa, $seq); 00836 00842 public function notify_step_deleted(question_attempt_step $step, question_attempt $qa); 00843 00844 } 00845 00846 00854 class question_usage_null_observer implements question_usage_observer { 00855 public function notify_modified() { 00856 } 00857 public function notify_attempt_modified(question_attempt $qa) { 00858 } 00859 public function notify_attempt_added(question_attempt $qa) { 00860 } 00861 public function notify_step_added(question_attempt_step $step, question_attempt $qa, $seq) { 00862 } 00863 public function notify_step_modified(question_attempt_step $step, question_attempt $qa, $seq) { 00864 } 00865 public function notify_step_deleted(question_attempt_step $step, question_attempt $qa) { 00866 } 00867 }