Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/question/engine/datalib.php
Go to the documentation of this file.
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 
00046 defined('MOODLE_INTERNAL') || die();
00047 
00048 
00056 class question_engine_data_mapper {
00061     protected $db;
00062 
00066     public function __construct($db = null) {
00067         if (is_null($db)) {
00068             global $DB;
00069             $this->db = $DB;
00070         } else {
00071             $this->db = $db;
00072         }
00073     }
00074 
00080     public function insert_questions_usage_by_activity(question_usage_by_activity $quba) {
00081         $record = new stdClass();
00082         $record->contextid = $quba->get_owning_context()->id;
00083         $record->component = $quba->get_owning_component();
00084         $record->preferredbehaviour = $quba->get_preferred_behaviour();
00085 
00086         $newid = $this->db->insert_record('question_usages', $record);
00087         $quba->set_id_from_database($newid);
00088 
00089         foreach ($quba->get_attempt_iterator() as $qa) {
00090             $this->insert_question_attempt($qa, $quba->get_owning_context());
00091         }
00092     }
00093 
00100     public function insert_question_attempt(question_attempt $qa, $context) {
00101         $record = new stdClass();
00102         $record->questionusageid = $qa->get_usage_id();
00103         $record->slot = $qa->get_slot();
00104         $record->behaviour = $qa->get_behaviour_name();
00105         $record->questionid = $qa->get_question()->id;
00106         $record->variant = $qa->get_variant();
00107         $record->maxmark = $qa->get_max_mark();
00108         $record->minfraction = $qa->get_min_fraction();
00109         $record->flagged = $qa->is_flagged();
00110         $record->questionsummary = $qa->get_question_summary();
00111         if (textlib::strlen($record->questionsummary) > question_bank::MAX_SUMMARY_LENGTH) {
00112             // It seems some people write very long quesions! MDL-30760
00113             $record->questionsummary = textlib::substr($record->questionsummary,
00114                     0, question_bank::MAX_SUMMARY_LENGTH - 3) . '...';
00115         }
00116         $record->rightanswer = $qa->get_right_answer_summary();
00117         $record->responsesummary = $qa->get_response_summary();
00118         $record->timemodified = time();
00119         $record->id = $this->db->insert_record('question_attempts', $record);
00120 
00121         foreach ($qa->get_step_iterator() as $seq => $step) {
00122             $this->insert_question_attempt_step($step, $record->id, $seq, $context);
00123         }
00124     }
00125 
00133     protected function make_step_record(question_attempt_step $step, $questionattemptid, $seq) {
00134         $record = new stdClass();
00135         $record->questionattemptid = $questionattemptid;
00136         $record->sequencenumber = $seq;
00137         $record->state = (string) $step->get_state();
00138         $record->fraction = $step->get_fraction();
00139         $record->timecreated = $step->get_timecreated();
00140         $record->userid = $step->get_user_id();
00141         return $record;
00142     }
00143 
00150     protected function insert_step_data(question_attempt_step $step, $stepid, $context) {
00151         foreach ($step->get_all_data() as $name => $value) {
00152             if ($value instanceof question_file_saver) {
00153                 $value->save_files($stepid, $context);
00154             }
00155 
00156             $data = new stdClass();
00157             $data->attemptstepid = $stepid;
00158             $data->name = $name;
00159             $data->value = $value;
00160             $this->db->insert_record('question_attempt_step_data', $data, false);
00161         }
00162     }
00163 
00171     public function insert_question_attempt_step(question_attempt_step $step,
00172             $questionattemptid, $seq, $context) {
00173 
00174         $record = $this->make_step_record($step, $questionattemptid, $seq);
00175         $record->id = $this->db->insert_record('question_attempt_steps', $record);
00176 
00177         $this->insert_step_data($step, $record->id, $context);
00178     }
00179 
00187     public function update_question_attempt_step(question_attempt_step $step,
00188             $questionattemptid, $seq, $context) {
00189 
00190         $record = $this->make_step_record($step, $questionattemptid, $seq);
00191         $record->id = $step->get_id();
00192         $this->db->update_record('question_attempt_steps', $record);
00193 
00194         $this->db->delete_records('question_attempt_step_data',
00195                 array('attemptstepid' => $record->id));
00196         $this->insert_step_data($step, $record->id, $context);
00197     }
00198 
00204     public function load_question_attempt_step($stepid) {
00205         $records = $this->db->get_recordset_sql("
00206 SELECT
00207     qas.id AS attemptstepid,
00208     qas.questionattemptid,
00209     qas.sequencenumber,
00210     qas.state,
00211     qas.fraction,
00212     qas.timecreated,
00213     qas.userid,
00214     qasd.name,
00215     qasd.value
00216 
00217 FROM {question_attempt_steps} qas
00218 LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
00219 
00220 WHERE
00221     qas.id = :stepid
00222         ", array('stepid' => $stepid));
00223 
00224         if (!$records->valid()) {
00225             throw new coding_exception('Failed to load question_attempt_step ' . $stepid);
00226         }
00227 
00228         $step = question_attempt_step::load_from_records($records, $stepid);
00229         $records->close();
00230 
00231         return $step;
00232     }
00233 
00240     public function load_question_attempt($questionattemptid) {
00241         $records = $this->db->get_recordset_sql("
00242 SELECT
00243     quba.contextid,
00244     quba.preferredbehaviour,
00245     qa.id AS questionattemptid,
00246     qa.questionusageid,
00247     qa.slot,
00248     qa.behaviour,
00249     qa.questionid,
00250     qa.variant,
00251     qa.maxmark,
00252     qa.minfraction,
00253     qa.flagged,
00254     qa.questionsummary,
00255     qa.rightanswer,
00256     qa.responsesummary,
00257     qa.timemodified,
00258     qas.id AS attemptstepid,
00259     qas.sequencenumber,
00260     qas.state,
00261     qas.fraction,
00262     qas.timecreated,
00263     qas.userid,
00264     qasd.name,
00265     qasd.value
00266 
00267 FROM      {question_attempts           qa
00268 JOIN      {question_usages}            quba ON quba.id               = qa.questionusageid
00269 LEFT JOIN {question_attempt_steps}     qas  ON qas.questionattemptid = qa.id
00270 LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid    = qas.id
00271 
00272 WHERE
00273     qa.id = :questionattemptid
00274 
00275 ORDER BY
00276     qas.sequencenumber
00277         ", array('questionattemptid' => $questionattemptid));
00278 
00279         if (!$records->valid()) {
00280             throw new coding_exception('Failed to load question_attempt ' . $questionattemptid);
00281         }
00282 
00283         $record = current($records);
00284         $qa = question_attempt::load_from_records($records, $questionattemptid,
00285                 new question_usage_null_observer(), $record->preferredbehaviour);
00286         $records->close();
00287 
00288         return $qa;
00289     }
00290 
00297     public function load_questions_usage_by_activity($qubaid) {
00298         $records = $this->db->get_recordset_sql("
00299 SELECT
00300     quba.id AS qubaid,
00301     quba.contextid,
00302     quba.component,
00303     quba.preferredbehaviour,
00304     qa.id AS questionattemptid,
00305     qa.questionusageid,
00306     qa.slot,
00307     qa.behaviour,
00308     qa.questionid,
00309     qa.variant,
00310     qa.maxmark,
00311     qa.minfraction,
00312     qa.flagged,
00313     qa.questionsummary,
00314     qa.rightanswer,
00315     qa.responsesummary,
00316     qa.timemodified,
00317     qas.id AS attemptstepid,
00318     qas.sequencenumber,
00319     qas.state,
00320     qas.fraction,
00321     qas.timecreated,
00322     qas.userid,
00323     qasd.name,
00324     qasd.value
00325 
00326 FROM      {question_usages}            quba
00327 LEFT JOIN {question_attempts}          qa   ON qa.questionusageid    = quba.id
00328 LEFT JOIN {question_attempt_steps}     qas  ON qas.questionattemptid = qa.id
00329 LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid    = qas.id
00330 
00331 WHERE
00332     quba.id = :qubaid
00333 
00334 ORDER BY
00335     qa.slot,
00336     qas.sequencenumber
00337     ", array('qubaid' => $qubaid));
00338 
00339         if (!$records->valid()) {
00340             throw new coding_exception('Failed to load questions_usage_by_activity ' . $qubaid);
00341         }
00342 
00343         $quba = question_usage_by_activity::load_from_records($records, $qubaid);
00344         $records->close();
00345 
00346         return $quba;
00347     }
00348 
00357     public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $slots) {
00358         list($slottest, $params) = $this->db->get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot');
00359 
00360         $records = $this->db->get_records_sql("
00361 SELECT
00362     qas.id,
00363     qa.id AS questionattemptid,
00364     qa.questionusageid,
00365     qa.slot,
00366     qa.behaviour,
00367     qa.questionid,
00368     qa.variant,
00369     qa.maxmark,
00370     qa.minfraction,
00371     qa.flagged,
00372     qa.questionsummary,
00373     qa.rightanswer,
00374     qa.responsesummary,
00375     qa.timemodified,
00376     qas.id AS attemptstepid,
00377     qas.sequencenumber,
00378     qas.state,
00379     qas.fraction,
00380     qas.timecreated,
00381     qas.userid
00382 
00383 FROM {$qubaids->from_question_attempts('qa')}
00384 JOIN {question_attempt_steps} qas ON
00385         qas.id = {$this->latest_step_for_qa_subquery()}
00386 
00387 WHERE
00388     {$qubaids->where()} AND
00389     qa.slot $slottest
00390         ", $params + $qubaids->from_where_params());
00391 
00392         return $records;
00393     }
00394 
00407     public function load_questions_usages_question_state_summary(
00408             qubaid_condition $qubaids, $slots) {
00409         list($slottest, $params) = $this->db->get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot');
00410 
00411         $rs = $this->db->get_recordset_sql("
00412 SELECT
00413     qa.slot,
00414     qa.questionid,
00415     q.name,
00416     CASE qas.state
00417         {$this->full_states_to_summary_state_sql()}
00418     END AS summarystate,
00419     COUNT(1) AS numattempts
00420 
00421 FROM {$qubaids->from_question_attempts('qa')}
00422 JOIN {question_attempt_steps} qas ON
00423         qas.id = {$this->latest_step_for_qa_subquery()}
00424 JOIN {question} q ON q.id = qa.questionid
00425 
00426 WHERE
00427     {$qubaids->where()} AND
00428     qa.slot $slottest
00429 
00430 GROUP BY
00431     qa.slot,
00432     qa.questionid,
00433     q.name,
00434     q.id,
00435     CASE qas.state
00436         {$this->full_states_to_summary_state_sql()}
00437     END
00438 
00439 ORDER BY
00440     qa.slot,
00441     qa.questionid,
00442     q.name,
00443     q.id
00444         ", $params + $qubaids->from_where_params());
00445 
00446         $results = array();
00447         foreach ($rs as $row) {
00448             $index = $row->slot . ',' . $row->questionid;
00449 
00450             if (!array_key_exists($index, $results)) {
00451                 $res = new stdClass();
00452                 $res->slot = $row->slot;
00453                 $res->questionid = $row->questionid;
00454                 $res->name = $row->name;
00455                 $res->inprogress = 0;
00456                 $res->needsgrading = 0;
00457                 $res->autograded = 0;
00458                 $res->manuallygraded = 0;
00459                 $res->all = 0;
00460                 $results[$index] = $res;
00461             }
00462 
00463             $results[$index]->{$row->summarystate} = $row->numattempts;
00464             $results[$index]->all += $row->numattempts;
00465         }
00466         $rs->close();
00467 
00468         return $results;
00469     }
00470 
00492     public function load_questions_usages_where_question_in_state(
00493             qubaid_condition $qubaids, $summarystate, $slot, $questionid = null,
00494             $orderby = 'random', $params, $limitfrom = 0, $limitnum = null) {
00495 
00496         $extrawhere = '';
00497         if ($questionid) {
00498             $extrawhere .= ' AND qa.questionid = :questionid';
00499             $params['questionid'] = $questionid;
00500         }
00501         if ($summarystate != 'all') {
00502             list($test, $sparams) = $this->in_summary_state_test($summarystate);
00503             $extrawhere .= ' AND qas.state ' . $test;
00504             $params += $sparams;
00505         }
00506 
00507         if ($orderby == 'random') {
00508             $sqlorderby = '';
00509         } else if ($orderby) {
00510             $sqlorderby = 'ORDER BY ' . $orderby;
00511         } else {
00512             $sqlorderby = '';
00513         }
00514 
00515         // We always want the total count, as well as the partcular list of ids,
00516         // based on the paging and sort order. Becuase the list of ids is never
00517         // going to be too rediculously long. My worst-case scenario is
00518         // 10,000 students in the coures, each doing 5 quiz attempts. That
00519         // is a 50,000 element int => int array, which PHP seems to use 5MB
00520         // memeory to store on a 64 bit server.
00521         $params += $qubaids->from_where_params();
00522         $params['slot'] = $slot;
00523         $qubaids = $this->db->get_records_sql_menu("
00524 SELECT
00525     qa.questionusageid,
00526     1
00527 
00528 FROM {$qubaids->from_question_attempts('qa')}
00529 JOIN {question_attempt_steps} qas ON
00530         qas.id = {$this->latest_step_for_qa_subquery()}
00531 JOIN {question} q ON q.id = qa.questionid
00532 
00533 WHERE
00534     {$qubaids->where()} AND
00535     qa.slot = :slot
00536     $extrawhere
00537 
00538 $sqlorderby
00539         ", $params);
00540 
00541         $qubaids = array_keys($qubaids);
00542         $count = count($qubaids);
00543 
00544         if ($orderby == 'random') {
00545             shuffle($qubaids);
00546             $limitfrom = 0;
00547         }
00548 
00549         if (!is_null($limitnum)) {
00550             $qubaids = array_slice($qubaids, $limitfrom, $limitnum);
00551         }
00552 
00553         return array($qubaids, $count);
00554     }
00555 
00564     public function load_average_marks(qubaid_condition $qubaids, $slots = null) {
00565         if (!empty($slots)) {
00566             list($slottest, $slotsparams) = $this->db->get_in_or_equal(
00567                     $slots, SQL_PARAMS_NAMED, 'slot');
00568             $slotwhere = " AND qa.slot $slottest";
00569         } else {
00570             $slotwhere = '';
00571             $params = array();
00572         }
00573 
00574         list($statetest, $stateparams) = $this->db->get_in_or_equal(array(
00575                 (string) question_state::$gaveup,
00576                 (string) question_state::$gradedwrong,
00577                 (string) question_state::$gradedpartial,
00578                 (string) question_state::$gradedright,
00579                 (string) question_state::$mangaveup,
00580                 (string) question_state::$mangrwrong,
00581                 (string) question_state::$mangrpartial,
00582                 (string) question_state::$mangrright), SQL_PARAMS_NAMED, 'st');
00583 
00584         return $this->db->get_records_sql("
00585 SELECT
00586     qa.slot,
00587     AVG(COALESCE(qas.fraction, 0)) AS averagefraction,
00588     COUNT(1) AS numaveraged
00589 
00590 FROM {$qubaids->from_question_attempts('qa')}
00591 JOIN {question_attempt_steps} qas ON
00592         qas.id = {$this->latest_step_for_qa_subquery()}
00593 
00594 WHERE
00595     {$qubaids->where()}
00596     $slotwhere
00597     AND qas.state $statetest
00598 
00599 GROUP BY qa.slot
00600 
00601 ORDER BY qa.slot
00602         ", $slotsparams + $stateparams + $qubaids->from_where_params());
00603     }
00604 
00613     public function load_attempts_at_question($questionid, qubaid_condition $qubaids) {
00614         global $DB;
00615 
00616         $params = $qubaids->from_where_params();
00617         $params['questionid'] = $questionid;
00618 
00619         $records = $DB->get_recordset_sql("
00620 SELECT
00621     quba.contextid,
00622     quba.preferredbehaviour,
00623     qa.id AS questionattemptid,
00624     qa.questionusageid,
00625     qa.slot,
00626     qa.behaviour,
00627     qa.questionid,
00628     qa.variant,
00629     qa.maxmark,
00630     qa.minfraction,
00631     qa.flagged,
00632     qa.questionsummary,
00633     qa.rightanswer,
00634     qa.responsesummary,
00635     qa.timemodified,
00636     qas.id AS attemptstepid,
00637     qas.sequencenumber,
00638     qas.state,
00639     qas.fraction,
00640     qas.timecreated,
00641     qas.userid,
00642     qasd.name,
00643     qasd.value
00644 
00645 FROM {$qubaids->from_question_attempts('qa')}
00646 JOIN {question_usages} quba ON quba.id = qa.questionusageid
00647 LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
00648 LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
00649 
00650 WHERE
00651     {$qubaids->where()} AND
00652     qa.questionid = :questionid
00653 
00654 ORDER BY
00655     quba.id,
00656     qa.id,
00657     qas.sequencenumber
00658         ", $params);
00659 
00660         $questionattempts = array();
00661         while ($records->valid()) {
00662             $record = $records->current();
00663             $questionattempts[$record->questionattemptid] =
00664                     question_attempt::load_from_records($records,
00665                     $record->questionattemptid, new question_usage_null_observer(),
00666                     $record->preferredbehaviour);
00667         }
00668         $records->close();
00669 
00670         return $questionattempts;
00671     }
00672 
00678     public function update_questions_usage_by_activity(question_usage_by_activity $quba) {
00679         $record = new stdClass();
00680         $record->id = $quba->get_id();
00681         $record->contextid = $quba->get_owning_context()->id;
00682         $record->component = $quba->get_owning_component();
00683         $record->preferredbehaviour = $quba->get_preferred_behaviour();
00684 
00685         $this->db->update_record('question_usages', $record);
00686     }
00687 
00693     public function update_question_attempt(question_attempt $qa) {
00694         $record = new stdClass();
00695         $record->id = $qa->get_database_id();
00696         $record->maxmark = $qa->get_max_mark();
00697         $record->minfraction = $qa->get_min_fraction();
00698         $record->flagged = $qa->is_flagged();
00699         $record->questionsummary = $qa->get_question_summary();
00700         $record->rightanswer = $qa->get_right_answer_summary();
00701         $record->responsesummary = $qa->get_response_summary();
00702         $record->timemodified = time();
00703 
00704         $this->db->update_record('question_attempts', $record);
00705     }
00706 
00713     public function delete_questions_usage_by_activities(qubaid_condition $qubaids) {
00714         $where = "qa.questionusageid {$qubaids->usage_id_in()}";
00715         $params = $qubaids->usage_id_in_params();
00716 
00717         $contextids = $this->db->get_records_sql_menu("
00718                 SELECT DISTINCT contextid, 1
00719                 FROM {question_usages}
00720                 WHERE id {$qubaids->usage_id_in()}", $qubaids->usage_id_in_params());
00721         foreach ($contextids as $contextid => $notused) {
00722             $this->delete_response_files($contextid, "IN (
00723                     SELECT qas.id
00724                     FROM {question_attempts} qa
00725                     JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
00726                     WHERE $where)", $params);
00727         }
00728 
00729         if ($this->db->get_dbfamily() == 'mysql') {
00730             $this->delete_usage_records_for_mysql($qubaids);
00731             return;
00732         }
00733 
00734         $this->db->delete_records_select('question_attempt_step_data', "attemptstepid IN (
00735                 SELECT qas.id
00736                 FROM {question_attempts} qa
00737                 JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
00738                 WHERE $where)", $params);
00739 
00740         $this->db->delete_records_select('question_attempt_steps', "questionattemptid IN (
00741                 SELECT qa.id
00742                 FROM {question_attempts} qa
00743                 WHERE $where)", $params);
00744 
00745         $this->db->delete_records_select('question_attempts',
00746                 "{question_attempts}.questionusageid {$qubaids->usage_id_in()}",
00747                 $qubaids->usage_id_in_params());
00748 
00749         $this->db->delete_records_select('question_usages',
00750                 "{question_usages}.id {$qubaids->usage_id_in()}", $qubaids->usage_id_in_params());
00751     }
00752 
00759     protected function delete_usage_records_for_mysql(qubaid_condition $qubaids) {
00760         // TODO once MDL-29589 is fixed, eliminate this method, and instead use the new $DB API.
00761         $this->db->execute('
00762                 DELETE qu, qa, qas, qasd
00763                   FROM {question_usages}            qu
00764                   JOIN {question_attempts}          qa   ON qa.questionusageid = qu.id
00765              LEFT JOIN {question_attempt_steps}     qas  ON qas.questionattemptid = qa.id
00766              LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
00767                  WHERE qu.id ' . $qubaids->usage_id_in(),
00768                 $qubaids->usage_id_in_params());
00769     }
00770 
00778     protected function delete_attempt_steps_for_mysql($test, $params) {
00779         // TODO once MDL-29589 is fixed, eliminate this method, and instead use the new $DB API.
00780         $this->db->execute('
00781                 DELETE qas, qasd
00782                   FROM {question_attempt_steps}     qas
00783              LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
00784                  WHERE qas.questionattemptid ' . $test, $params);
00785     }
00786 
00792     public function delete_steps($stepids, $context) {
00793         if (empty($stepids)) {
00794             return;
00795         }
00796         list($test, $params) = $this->db->get_in_or_equal($stepids, SQL_PARAMS_NAMED);
00797 
00798         if ($deletefiles) {
00799             $this->delete_response_files($context->id, $test, $params);
00800         }
00801 
00802         if ($this->db->get_dbfamily() == 'mysql') {
00803             $this->delete_attempt_steps_for_mysql($test, $params);
00804             return;
00805         }
00806 
00807         $this->db->delete_records_select('question_attempt_step_data',
00808                 "attemptstepid $test", $params);
00809         $this->db->delete_records_select('question_attempt_steps',
00810                 "attemptstepid $test", $params);
00811     }
00812 
00821     protected function delete_response_files($contextid, $itemidstest, $params) {
00822         $fs = get_file_storage();
00823         foreach (question_engine::get_all_response_file_areas() as $filearea) {
00824             $fs->delete_area_files_select($contextid, 'question', $filearea,
00825                     $itemidstest, $params);
00826         }
00827     }
00828 
00833     public function delete_previews($questionid) {
00834         $previews = $this->db->get_records_sql_menu("
00835                 SELECT DISTINCT quba.id, 1
00836                 FROM {question_usages} quba
00837                 JOIN {question_attempts} qa ON qa.questionusageid = quba.id
00838                 WHERE quba.component = 'core_question_preview' AND
00839                     qa.questionid = ?", array($questionid));
00840         if (empty($previews)) {
00841             return;
00842         }
00843         $this->delete_questions_usage_by_activities(new qubaid_list($previews));
00844     }
00845 
00853     public function update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate) {
00854         if (!$this->db->record_exists('question_attempts', array('id' => $qaid,
00855                 'questionusageid' => $qubaid, 'questionid' => $questionid, 'slot' => $slot))) {
00856             throw new moodle_exception('errorsavingflags', 'question');
00857         }
00858 
00859         $this->db->set_field('question_attempts', 'flagged', $newstate, array('id' => $qaid));
00860     }
00861 
00868     protected function full_states_to_summary_state_sql() {
00869         $sql = '';
00870         foreach (question_state::get_all() as $state) {
00871             $sql .= "WHEN '$state' THEN '{$state->get_summary_state()}'\n";
00872         }
00873         return $sql;
00874     }
00875 
00884     public function in_summary_state_test($summarystate, $equal = true, $prefix = 'summarystates') {
00885         $states = question_state::get_all_for_summary_state($summarystate);
00886         return $this->db->get_in_or_equal(array_map('strval', $states),
00887                 SQL_PARAMS_NAMED, $prefix, $equal);
00888     }
00889 
00897     public function set_max_mark_in_attempts(qubaid_condition $qubaids, $slot, $newmaxmark) {
00898         $this->db->set_field_select('question_attempts', 'maxmark', $newmaxmark,
00899                 "questionusageid {$qubaids->usage_id_in()} AND slot = :slot",
00900                 $qubaids->usage_id_in_params() + array('slot' => $slot));
00901     }
00902 
00916     public function sum_usage_marks_subquery($qubaid) {
00917         // To explain the COALESCE in the following SQL: SUM(lots of NULLs) gives
00918         // NULL, while SUM(one 0.0 and lots of NULLS) gives 0.0. We don't want that.
00919         // We always want to return a number, so the COALESCE is there to turn the
00920         // NULL total into a 0.
00921         return "SELECT COALESCE(SUM(qa.maxmark * qas.fraction), 0)
00922             FROM {question_attempts} qa
00923             JOIN {question_attempt_steps} qas ON qas.id = (
00924                 SELECT MAX(summarks_qas.id)
00925                   FROM {question_attempt_steps} summarks_qas
00926                  WHERE summarks_qas.questionattemptid = qa.id
00927             )
00928             WHERE qa.questionusageid = $qubaid
00929             HAVING COUNT(CASE
00930                 WHEN qas.state = 'needsgrading' AND qa.maxmark > 0 THEN 1
00931                 ELSE NULL
00932             END) = 0";
00933     }
00934 
00944     public function question_attempt_latest_state_view($alias, qubaid_condition $qubaids) {
00945         return array("(
00946                 SELECT {$alias}qa.id AS questionattemptid,
00947                        {$alias}qa.questionusageid,
00948                        {$alias}qa.slot,
00949                        {$alias}qa.behaviour,
00950                        {$alias}qa.questionid,
00951                        {$alias}qa.variant,
00952                        {$alias}qa.maxmark,
00953                        {$alias}qa.minfraction,
00954                        {$alias}qa.flagged,
00955                        {$alias}qa.questionsummary,
00956                        {$alias}qa.rightanswer,
00957                        {$alias}qa.responsesummary,
00958                        {$alias}qa.timemodified,
00959                        {$alias}qas.id AS attemptstepid,
00960                        {$alias}qas.sequencenumber,
00961                        {$alias}qas.state,
00962                        {$alias}qas.fraction,
00963                        {$alias}qas.timecreated,
00964                        {$alias}qas.userid
00965 
00966                   FROM {$qubaids->from_question_attempts($alias . 'qa')}
00967                   JOIN {question_attempt_steps} {$alias}qas ON
00968                            {$alias}qas.id = {$this->latest_step_for_qa_subquery($alias . 'qa.id')}
00969                  WHERE {$qubaids->where()}
00970             ) $alias", $qubaids->from_where_params());
00971     }
00972 
00973     protected function latest_step_for_qa_subquery($questionattemptid = 'qa.id') {
00974         return "(
00975                 SELECT MAX(id)
00976                 FROM {question_attempt_steps}
00977                 WHERE questionattemptid = $questionattemptid
00978             )";
00979     }
00980 
00987     public function questions_in_use(array $questionids, qubaid_condition $qubaids) {
00988         list($test, $params) = $this->db->get_in_or_equal($questionids);
00989         return $this->db->record_exists_select('question_attempts',
00990                 'questionid ' . $test . ' AND questionusageid ' .
00991                 $qubaids->usage_id_in(), $params + $qubaids->usage_id_in_params());
00992     }
00993 }
00994 
00995 
01006 class question_engine_unit_of_work implements question_usage_observer {
01008     protected $quba;
01009 
01011     protected $modified = false;
01012 
01017     protected $attemptsmodified = array();
01018 
01023     protected $attemptsadded = array();
01024 
01029     protected $stepsadded = array();
01030 
01035     protected $stepsmodified = array();
01036 
01041     protected $stepsdeleted = array();
01042 
01047     public function __construct(question_usage_by_activity $quba) {
01048         $this->quba = $quba;
01049     }
01050 
01051     public function notify_modified() {
01052         $this->modified = true;
01053     }
01054 
01055     public function notify_attempt_modified(question_attempt $qa) {
01056         $slot = $qa->get_slot();
01057         if (!array_key_exists($slot, $this->attemptsadded)) {
01058             $this->attemptsmodified[$slot] = $qa;
01059         }
01060     }
01061 
01062     public function notify_attempt_added(question_attempt $qa) {
01063         $this->attemptsadded[$qa->get_slot()] = $qa;
01064     }
01065 
01066     public function notify_step_added(question_attempt_step $step, question_attempt $qa, $seq) {
01067         if (array_key_exists($qa->get_slot(), $this->attemptsadded)) {
01068             return;
01069         }
01070 
01071         if (($key = $this->is_step_added($step)) !== false) {
01072             return;
01073         }
01074 
01075         if (($key = $this->is_step_modified($step)) !== false) {
01076             throw new coding_exception('Cannot add a step that has already been modified.');
01077         }
01078 
01079         if (($key = $this->is_step_deleted($step)) !== false) {
01080             unset($this->stepsdeleted[$step->get_id()]);
01081             $this->stepsmodified[] = array($step, $qa->get_database_id(), $seq);
01082             return;
01083         }
01084 
01085         $stepid = $step->get_id();
01086         if ($stepid) {
01087             if (array_key_exists($stepid, $this->stepsdeleted)) {
01088                 unset($this->stepsdeleted[$stepid]);
01089             }
01090             $this->stepsmodified[] = array($step, $qa->get_database_id(), $seq);
01091 
01092         } else {
01093             $this->stepsadded[] = array($step, $qa->get_database_id(), $seq);
01094         }
01095     }
01096 
01097     public function notify_step_modified(question_attempt_step $step, question_attempt $qa, $seq) {
01098         if (array_key_exists($qa->get_slot(), $this->attemptsadded)) {
01099             return;
01100         }
01101 
01102         if (($key = $this->is_step_added($step)) !== false) {
01103             return;
01104         }
01105 
01106         if (($key = $this->is_step_deleted($step)) !== false) {
01107             throw new coding_exception('Cannot modify a step after it has been deleted.');
01108         }
01109 
01110         $stepid = $step->get_id();
01111         if (empty($stepid)) {
01112             throw new coding_exception('Cannot modify a step that has never been stored in the database.');
01113         }
01114 
01115         $this->stepsmodified[] = array($step, $qa->get_database_id(), $seq);
01116     }
01117 
01118     public function notify_step_deleted(question_attempt_step $step, question_attempt $qa) {
01119         if (array_key_exists($qa->get_slot(), $this->attemptsadded)) {
01120             return;
01121         }
01122 
01123         if (($key = $this->is_step_added($step)) !== false) {
01124             unset($this->stepsadded[$key]);
01125             return;
01126         }
01127 
01128         if (($key = $this->is_step_modified($step)) !== false) {
01129             unset($this->stepsmodified[$key]);
01130         }
01131 
01132         $stepid = $step->get_id();
01133         if (empty($stepid)) {
01134             return; // Was never in the database.
01135         }
01136 
01137         $this->stepsdeleted[$stepid] = $step;
01138     }
01139 
01145     protected function is_step_added(question_attempt_step $step) {
01146         foreach ($this->stepsadded as $key => $data) {
01147             list($addedstep, $qaid, $seq) = $data;
01148             if ($addedstep === $step) {
01149                 return $key;
01150             }
01151         }
01152         return false;
01153     }
01154 
01160     protected function is_step_modified(question_attempt_step $step) {
01161         foreach ($this->stepsmodified as $key => $data) {
01162             list($modifiedstep, $qaid, $seq) = $data;
01163             if ($modifiedstep === $step) {
01164                 return $key;
01165             }
01166         }
01167         return false;
01168     }
01169 
01174     protected function is_step_deleted(question_attempt_step $step) {
01175         foreach ($this->stepsdeleted as $deletedstep) {
01176             if ($deletedstep === $step) {
01177                 return true;
01178             }
01179         }
01180         return false;
01181     }
01182 
01187     public function save(question_engine_data_mapper $dm) {
01188         $dm->delete_steps(array_keys($this->stepsdeleted), $this->quba->get_owning_context());
01189 
01190         foreach ($this->stepsmodified as $stepinfo) {
01191             list($step, $questionattemptid, $seq) = $stepinfo;
01192             $dm->update_question_attempt_step($step, $questionattemptid, $seq,
01193                     $this->quba->get_owning_context());
01194         }
01195 
01196         foreach ($this->stepsadded as $stepinfo) {
01197             list($step, $questionattemptid, $seq) = $stepinfo;
01198             $dm->insert_question_attempt_step($step, $questionattemptid, $seq,
01199                     $this->quba->get_owning_context());
01200         }
01201 
01202         foreach ($this->attemptsadded as $qa) {
01203             $dm->insert_question_attempt($qa, $this->quba->get_owning_context());
01204         }
01205 
01206         foreach ($this->attemptsmodified as $qa) {
01207             $dm->update_question_attempt($qa);
01208         }
01209 
01210         if ($this->modified) {
01211             $dm->update_questions_usage_by_activity($this->quba);
01212         }
01213     }
01214 }
01215 
01216 
01228 class question_file_saver {
01230     protected $draftitemid;
01232     protected $component;
01234     protected $filearea;
01235 
01240     protected $value = null;
01241 
01248     public function __construct($draftitemid, $component, $filearea, $text = null) {
01249         $this->draftitemid = $draftitemid;
01250         $this->component = $component;
01251         $this->filearea = $filearea;
01252         $this->value = $this->compute_value($draftitemid, $text);
01253     }
01254 
01261     protected function compute_value($draftitemid, $text) {
01262         global $USER;
01263 
01264         $fs = get_file_storage();
01265         $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
01266 
01267         $files = $fs->get_area_files($usercontext->id, 'user', 'draft',
01268                 $draftitemid, 'sortorder, filepath, filename', false);
01269 
01270         $string = '';
01271         foreach ($files as $file) {
01272             $string .= $file->get_filepath() . $file->get_filename() . '|' .
01273                     $file->get_contenthash() . '|';
01274         }
01275 
01276         if ($string) {
01277             $hash = md5($string);
01278         } else {
01279             $hash = '';
01280         }
01281 
01282         if (is_null($text)) {
01283             return $hash;
01284         }
01285 
01286         // We add the file hash so a simple string comparison will say if the
01287         // files have been changed. First strip off any existing file hash.
01288         $text = preg_replace('/\s*<!-- File hash: \w+ -->\s*$/', '', $text);
01289         $text = file_rewrite_urls_to_pluginfile($text, $draftitemid);
01290         if ($hash) {
01291             $text .= '<!-- File hash: ' . $hash . ' -->';
01292         }
01293         return $text;
01294     }
01295 
01296     public function __toString() {
01297         return $this->value;
01298     }
01299 
01304     public function save_files($itemid, $context) {
01305         file_save_draft_area_files($this->draftitemid, $context->id,
01306                 $this->component, $this->filearea, $itemid);
01307     }
01308 }
01309 
01310 
01325 abstract class qubaid_condition {
01326 
01332     public abstract function from_question_attempts($alias);
01333 
01335     public abstract function where();
01336 
01341     public abstract function from_where_params();
01342 
01347     public abstract function usage_id_in();
01348 
01352     public abstract function usage_id_in_params();
01353 }
01354 
01355 
01363 class qubaid_list extends qubaid_condition {
01365     protected $qubaids;
01366     protected $columntotest = null;
01367     protected $params;
01368 
01373     public function __construct(array $qubaids) {
01374         $this->qubaids = $qubaids;
01375     }
01376 
01377     public function from_question_attempts($alias) {
01378         $this->columntotest = $alias . '.questionusageid';
01379         return '{question_attempts} ' . $alias;
01380     }
01381 
01382     public function where() {
01383         global $DB;
01384 
01385         if (is_null($this->columntotest)) {
01386             throw new coding_exception('Must call from_question_attempts before where().');
01387         }
01388         if (empty($this->qubaids)) {
01389             $this->params = array();
01390             return '1 = 0';
01391         }
01392 
01393         return $this->columntotest . ' ' . $this->usage_id_in();
01394     }
01395 
01396     public function from_where_params() {
01397         return $this->params;
01398     }
01399 
01400     public function usage_id_in() {
01401         global $DB;
01402 
01403         if (empty($this->qubaids)) {
01404             $this->params = array();
01405             return '= 0';
01406         }
01407         list($where, $this->params) = $DB->get_in_or_equal(
01408                 $this->qubaids, SQL_PARAMS_NAMED, 'qubaid');
01409         return $where;
01410     }
01411 
01412     public function usage_id_in_params() {
01413         return $this->params;
01414     }
01415 }
01416 
01417 
01434 class qubaid_join extends qubaid_condition {
01435     public $from;
01436     public $usageidcolumn;
01437     public $where;
01438     public $params;
01439 
01448     public function __construct($from, $usageidcolumn, $where = '', $params = array()) {
01449         $this->from = $from;
01450         $this->usageidcolumn = $usageidcolumn;
01451         $this->params = $params;
01452         if (empty($where)) {
01453             $where = '1 = 1';
01454         }
01455         $this->where = $where;
01456     }
01457 
01458     public function from_question_attempts($alias) {
01459         return "$this->from
01460                 JOIN {question_attempts} {$alias} ON " .
01461                         "{$alias}.questionusageid = $this->usageidcolumn";
01462     }
01463 
01464     public function where() {
01465         return $this->where;
01466     }
01467 
01468     public function from_where_params() {
01469         return $this->params;
01470     }
01471 
01472     public function usage_id_in() {
01473         return "IN (SELECT $this->usageidcolumn FROM $this->from WHERE $this->where)";
01474     }
01475 
01476     public function usage_id_in_params() {
01477         return $this->params;
01478     }
01479 }
 All Data Structures Namespaces Files Functions Variables Enumerations