protected function load_extra_data()
 {
     if (count($this->rawdata) === 0) {
         return;
     }
     $qubaids = $this->get_qubaids_condition();
     $dm = new question_engine_data_mapper();
     $this->questionusagesbyactivity = $dm->load_questions_usages_by_activity($qubaids);
     // Insert an extra field in attempt data and extra rows where necessary.
     $newrawdata = array();
     foreach ($this->rawdata as $attempt) {
         $maxtriesinanyslot = 1;
         foreach ($this->questionusagesbyactivity[$attempt->usageid]->get_slots() as $slot) {
             $tries = $this->get_no_of_tries($attempt, $slot);
             $maxtriesinanyslot = max($maxtriesinanyslot, $tries);
         }
         for ($try = 1; $try <= $maxtriesinanyslot; $try++) {
             $newtablerow = clone $attempt;
             $newtablerow->lasttryforallparts = $try == $maxtriesinanyslot;
             if ($try !== $maxtriesinanyslot) {
                 $newtablerow->state = quiz_attempt::IN_PROGRESS;
             }
             $newtablerow->try = $try;
             $newrawdata[] = $newtablerow;
             if ($this->options->whichtries == question_attempt::FIRST_TRY) {
                 break;
             }
         }
     }
     $this->rawdata = $newrawdata;
 }
Example #2
0
 /**
  * We create two usages, each with two questions, a short-answer marked
  * out of 5, and and essay marked out of 10. We just start these attempts.
  *
  * Then we change the max mark for the short-answer question in one of the
  * usages to 20, using a qubaid_list, and verify.
  *
  * Then we change the max mark for the essay question in the other
  * usage to 2, using a qubaid_join, and verify.
  */
 public function test_set_max_mark_in_attempts()
 {
     // Set up some things the tests will need.
     $this->resetAfterTest();
     $dm = new question_engine_data_mapper();
     // Create the questions.
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $sa = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $essay = $generator->create_question('essay', null, array('category' => $cat->id));
     // Create the first usage.
     $q = question_bank::load_question($sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $q = question_bank::load_question($essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $usage1id = $this->quba->get_id();
     // Create the second usage.
     $this->quba = question_engine::make_questions_usage_by_activity('unit_test', context_system::instance());
     $q = question_bank::load_question($sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $this->process_submission(array('answer' => 'fish'));
     $q = question_bank::load_question($essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $usage2id = $this->quba->get_id();
     // Test set_max_mark_in_attempts with a qubaid_list.
     $usagestoupdate = new qubaid_list(array($usage1id));
     $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(10, $quba2->get_question_max_mark(2));
     // Test set_max_mark_in_attempts with a qubaid_join.
     $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 'qu.id = :usageid', array('usageid' => $usage2id));
     $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(2, $quba2->get_question_max_mark(2));
     // Test the nothing to do case.
     $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 'qu.id = :usageid', array('usageid' => -1));
     $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(2, $quba2->get_question_max_mark(2));
 }
 public function load_latest_steps($attempts)
 {
     $qubaids = array();
     foreach ($attempts as $attempt) {
         $qubaids[] = $attempt->uniqueid;
     }
     $dm = new question_engine_data_mapper();
     $qubaidcondition = new qubaid_list($qubaids);
     $slots = array_filter(explode(',', $attempt->layout));
     $lateststeps = $dm->load_questions_usages_latest_steps($qubaidcondition, $slots);
     $this->lateststeps = array();
     foreach ($lateststeps as $step) {
         $this->lateststeps[$step->questionusageid][$step->slot] = $step;
     }
 }
    protected function dotest_question_attempt_latest_state_view() {
        global $DB;

        list($inlineview, $viewparams) = $this->dm->question_attempt_latest_state_view(
                'lateststate', $this->bothusages);

        $rawstates = $DB->get_records_sql("
                SELECT lateststate.questionattemptid,
                       qu.id AS questionusageid,
                       lateststate.slot,
                       lateststate.questionid,
                       lateststate.maxmark,
                       lateststate.sequencenumber,
                       lateststate.state
                  FROM {question_usages} qu
             LEFT JOIN $inlineview ON lateststate.questionusageid = qu.id
                 WHERE qu.id IN ({$this->usageids[0]}, {$this->usageids[1]})", $viewparams);

        $states = array();
        foreach ($rawstates as $state) {
            $states[$state->questionusageid][$state->slot] = $state;
            unset($state->questionattemptid);
            unset($state->questionusageid);
            unset($state->slot);
        }

        $state = $states[$this->usageids[0]][$this->allslots[0]];
        $this->assertEquals((object) array(
            'questionid'     => $this->sa->id,
            'maxmark'        => '5.0000000',
            'sequencenumber' => 2,
            'state'          => (string) question_state::$gradedright,
        ), $state);

        $state = $states[$this->usageids[0]][$this->allslots[1]];
        $this->assertEquals((object) array(
            'questionid'     => $this->essay->id,
            'maxmark'        => '10.0000000',
            'sequencenumber' => 2,
            'state'          => (string) question_state::$needsgrading,
        ), $state);

        $state = $states[$this->usageids[1]][$this->allslots[0]];
        $this->assertEquals((object) array(
            'questionid'     => $this->sa->id,
            'maxmark'        => '5.0000000',
            'sequencenumber' => 2,
            'state'          => (string) question_state::$gradedwrong,
        ), $state);

        $state = $states[$this->usageids[1]][$this->allslots[1]];
        $this->assertEquals((object) array(
            'questionid'     => $this->essay->id,
            'maxmark'        => '10.0000000',
            'sequencenumber' => 1,
            'state'          => (string) question_state::$gaveup,
        ), $state);
    }
 /**
  * Add an average grade over the attempts of a set of users.
  * @param string $label the title ot use for this row.
  * @param array $users the users to average over.
  */
 protected function add_average_row($label, $users)
 {
     global $DB;
     list($fields, $from, $where, $params) = $this->base_sql($users);
     $record = $DB->get_record_sql("\n                SELECT AVG(quiza.sumgrades) AS grade, COUNT(quiza.sumgrades) AS numaveraged\n                  FROM {$from}\n                 WHERE {$where}", $params);
     $record->grade = quiz_rescale_grade($record->grade, $this->quiz, false);
     if ($this->is_downloading()) {
         $namekey = 'lastname';
     } else {
         $namekey = 'fullname';
     }
     $averagerow = array($namekey => $label, 'sumgrades' => $this->format_average($record), 'feedbacktext' => strip_tags(quiz_report_feedback_for_grade($record->grade, $this->quiz->id, $this->context)));
     if ($this->options->slotmarks) {
         $dm = new question_engine_data_mapper();
         $qubaids = new qubaid_join($from, 'quiza.uniqueid', $where, $params);
         $avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions));
         $averagerow += $this->format_average_grade_for_questions($avggradebyq);
     }
     $this->add_data_keyed($averagerow);
 }
 /**
  * Analyse all the response data for for all the specified attempts at
  * this question.
  * @param $qubaids which attempts to consider.
  */
 public function analyse($qubaids)
 {
     // Load data.
     $dm = new question_engine_data_mapper();
     $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
     // Analyse it.
     foreach ($questionattempts as $qa) {
         $this->add_data_from_one_attempt($qa);
     }
     $this->loaded = true;
 }
Example #7
0
 /**
  * If the request seems valid, update the flag state of a question attempt.
  * Throws exceptions if this is not a valid update request.
  * @param int $qubaid the question usage id.
  * @param int $questionid the question id.
  * @param int $sessionid the question_attempt id.
  * @param string $checksum checksum, as computed by {@link get_toggle_checksum()}
  *      corresponding to the last three arguments.
  * @param bool $newstate the new state of the flag. true = flagged.
  */
 public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate)
 {
     // Check the checksum - it is very hard to know who a question session belongs
     // to, so we require that checksum parameter is matches an md5 hash of the
     // three ids and the users username. Since we are only updating a flag, that
     // probably makes it sufficiently difficult for malicious users to toggle
     // other users flags.
     if ($checksum != self::get_toggle_checksum($qubaid, $questionid, $qaid, $slot)) {
         throw new moodle_exception('errorsavingflags', 'question');
     }
     $dm = new question_engine_data_mapper();
     $dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate);
 }
Example #8
0
 /**
  * Get the latest step data from the db, from which we will calculate stats.
  *
  * @param \qubaid_condition $qubaids Which question usages to get the latest steps for?
  * @return array with two items
  *              - $lateststeps array of latest step data for the question usages
  *              - $summarks    array of total marks for each usage, indexed by usage id
  */
 protected function get_latest_steps($qubaids)
 {
     $dm = new \question_engine_data_mapper();
     $fields = "    qas.id,\n    qa.questionusageid,\n    qa.questionid,\n    qa.variant,\n    qa.slot,\n    qa.maxmark,\n    qas.fraction * qa.maxmark as mark";
     $lateststeps = $dm->load_questions_usages_latest_steps($qubaids, $this->stats->get_all_slots(), $fields);
     $summarks = array();
     if ($lateststeps) {
         foreach ($lateststeps as $step) {
             if (!isset($summarks[$step->questionusageid])) {
                 $summarks[$step->questionusageid] = 0;
             }
             $summarks[$step->questionusageid] += $step->mark;
         }
     }
     return array($lateststeps, $summarks);
 }
    /**
     * Add the information about the latest state of the question with slot
     * $slot to the query.
     *
     * The extra information is added as a join to a
     * 'table' with alias qa$slot, with columns that are a union of
     * the columns of the question_attempts and question_attempts_states tables.
     *
     * @param int $slot the question to add information for.
     */
    protected function add_latest_state_join($slot) {
        $alias = 'qa' . $slot;

        $fields = $this->get_required_latest_state_fields($slot, $alias);
        if (!$fields) {
            return;
        }

        // This condition roughly filters the list of attempts to be considered.
        // It is only used in a subselect to help crappy databases (see MDL-30122)
        // therefore, it is better to use a very simple join, which may include
        // too many records, than to do a super-accurate join.
        $qubaids = new qubaid_join("{quiz_attempts} {$alias}quiza", "{$alias}quiza.uniqueid",
                "{$alias}quiza.quiz = :{$alias}quizid", array("{$alias}quizid" => $this->sql->params['quizid']));

        $dm = new question_engine_data_mapper();
        list($inlineview, $viewparams) = $dm->question_attempt_latest_state_view($alias, $qubaids);

        $this->sql->fields .= ",\n$fields";
        $this->sql->from .= "\nLEFT JOIN $inlineview ON " .
                "$alias.questionusageid = quiza.uniqueid AND $alias.slot = :{$alias}slot";
        $this->sql->params[$alias . 'slot'] = $slot;
        $this->sql->params = array_merge($this->sql->params, $viewparams);
    }
Example #10
0
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack.  If not, see <http://www.gnu.org/licenses/>.
/**
 * Handles ajax requests to validate the input.
 */
define('AJAX_SCRIPT', true);
require_once __DIR__ . '/../../../../../config.php';
require_once $CFG->libdir . '/questionlib.php';
require_once __DIR__ . '/../options.class.php';
require_once __DIR__ . '/inputbase.class.php';
$qaid = required_param('qaid', PARAM_INT);
$inputname = required_param('name', PARAM_ALPHANUMEXT);
$inputvalue = required_param('input', PARAM_RAW);
if (!isloggedin()) {
    die;
}
// This should not be necessary, but the TeX filter requires it, because it uses $OUTPUT.
$PAGE->set_context(context_system::instance());
$dm = new question_engine_data_mapper();
$qa = $dm->load_question_attempt($qaid);
$question = $qa->get_question();
$input = $question->inputs[$inputname];
$state = $question->get_input_state($inputname, $input->maxima_to_response_array($inputvalue));
$result = array('input' => $inputvalue, 'status' => $state->status, 'message' => $input->render_validation($state, $qa->get_qt_field_name($inputname)));
header('Content-type: application/json; charset=utf-8');
echo json_encode($result);
Example #11
0
function quiz_update_all_attempt_sumgrades($quiz)
{
    global $DB;
    $dm = new question_engine_data_mapper();
    $timenow = time();
    $sql = "UPDATE {quiz_attempts}\n            SET\n                timemodified = :timenow,\n                sumgrades = (\n                    {$dm->sum_usage_marks_subquery('uniqueid')}\n                )\n            WHERE quiz = :quizid AND timefinish <> 0";
    $DB->execute($sql, array('timenow' => $timenow, 'quizid' => $quiz->id));
}
Example #12
0
 /**
  * Write all the changes we have recorded to the database.
  * @param question_engine_data_mapper $dm the mapper to use to update the database.
  */
 public function save(question_engine_data_mapper $dm)
 {
     $dm->delete_steps(array_keys($this->stepsdeleted), $this->quba->get_owning_context());
     foreach ($this->stepsmodified as $stepinfo) {
         list($step, $questionattemptid, $seq) = $stepinfo;
         $dm->update_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context());
     }
     foreach ($this->stepsadded as $stepinfo) {
         list($step, $questionattemptid, $seq) = $stepinfo;
         $dm->insert_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context());
     }
     foreach ($this->attemptsadded as $qa) {
         $dm->insert_question_attempt($qa, $this->quba->get_owning_context());
     }
     foreach ($this->attemptsmodified as $qa) {
         $dm->update_question_attempt($qa);
     }
     if ($this->modified) {
         $dm->update_questions_usage_by_activity($this->quba);
     }
 }
 /**
  * @param $qubaids \qubaid_condition
  * @return array with three items
  *              - $lateststeps array of latest step data for the question usages
  *              - $summarks    array of total marks for each usage, indexed by usage id
  *              - $summarksavg the average of the total marks over all the usages
  */
 protected function get_latest_steps($qubaids)
 {
     $dm = new \question_engine_data_mapper();
     $fields = "    qas.id,\n    qa.questionusageid,\n    qa.questionid,\n    qa.slot,\n    qa.maxmark,\n    qas.fraction * qa.maxmark as mark";
     $lateststeps = $dm->load_questions_usages_latest_steps($qubaids, array_keys($this->questionstats), $fields);
     $summarks = array();
     if ($lateststeps) {
         foreach ($lateststeps as $step) {
             if (!isset($summarks[$step->questionusageid])) {
                 $summarks[$step->questionusageid] = 0;
             }
             $summarks[$step->questionusageid] += $step->mark;
         }
         $summarksavg = array_sum($summarks) / count($summarks);
     } else {
         $summarksavg = null;
     }
     return array($lateststeps, $summarks, $summarksavg);
 }
Example #14
0
 /**
  * Get a list of usage ids where the question with slot $slot, and optionally
  * also with question id $questionid, is in summary state $summarystate. Also
  * return the total count of such states.
  *
  * Only a subset of the ids can be returned by using $orderby, $limitfrom and
  * $limitnum. A special value 'random' can be passed as $orderby, in which case
  * $limitfrom is ignored.
  *
  * @param int $slot The slot for the questions you want to konw about.
  * @param int $questionid (optional) Only return attempts that were of this specific question.
  * @param string $summarystate 'all', 'needsgrading', 'autograded' or 'manuallygraded'.
  * @param string $orderby 'random', 'date', 'student' or 'idnumber'.
  * @param int $page implements paging of the results.
  *      Ignored if $orderby = random or $pagesize is null.
  * @param int $pagesize implements paging of the results. null = all.
  */
 protected function get_usage_ids_where_question_in_state($summarystate, $slot, $questionid = null, $orderby = 'random', $page = 0, $pagesize = null)
 {
     global $CFG, $DB;
     $dm = new question_engine_data_mapper();
     if ($pagesize && $orderby != 'random') {
         $limitfrom = $page * $pagesize;
     } else {
         $limitfrom = 0;
     }
     $qubaids = $this->get_qubaids_condition();
     $params = array();
     if ($orderby == 'date') {
         list($statetest, $params) = $dm->in_summary_state_test('manuallygraded', false, 'mangrstate');
         $orderby = "(\n                    SELECT MAX(sortqas.timecreated)\n                    FROM {question_attempt_steps} sortqas\n                    WHERE sortqas.questionattemptid = qa.id\n                        AND sortqas.state {$statetest}\n                    )";
     } else {
         if ($orderby == 'studentfirstname' || $orderby == 'studentlastname' || $orderby == 'idnumber') {
             $qubaids->from .= " JOIN {user} u ON quiza.userid = u.id ";
             // For name sorting, map orderby form value to
             // actual column names; 'idnumber' maps naturally
             switch ($orderby) {
                 case "studentlastname":
                     $orderby = "u.lastname, u.firstname";
                     break;
                 case "studentfirstname":
                     $orderby = "u.firstname, u.lastname";
                     break;
             }
         }
     }
     return $dm->load_questions_usages_where_question_in_state($qubaids, $summarystate, $slot, $questionid, $orderby, $params, $limitfrom, $pagesize);
 }
 /**
  * Add the information about the latest state of the question with slot
  * $slot to the query.
  *
  * The extra information is added as a join to a
  * 'table' with alias qa$slot, with columns that are a union of
  * the columns of the question_attempts and question_attempts_states tables.
  *
  * @param int $slot the question to add information for.
  */
 protected function add_latest_state_join($slot)
 {
     $alias = 'qa' . $slot;
     $fields = $this->get_required_latest_state_fields($slot, $alias);
     if (!$fields) {
         return;
     }
     $dm = new question_engine_data_mapper();
     $inlineview = $dm->question_attempt_latest_state_view($alias);
     $this->sql->fields .= ",\n{$fields}";
     $this->sql->from .= "\nLEFT JOIN {$inlineview} ON " . "{$alias}.questionusageid = quiza.uniqueid AND {$alias}.slot = :{$alias}slot";
     $this->sql->params[$alias . 'slot'] = $slot;
 }
Example #16
0
 /**
  * Display analysis of a particular question in this quiz.
  * @param object $question the row from the question table for the question to analyse.
  */
 public function display_analysis($question)
 {
     get_question_options($question);
     $this->display_question_information($question);
     $dm = new question_engine_data_mapper();
     $this->attempts = $dm->load_attempts_at_question($question->id, $this->qubaids);
     // Setup useful internal arrays for report generation.
     $this->inputs = array_keys($question->inputs);
     $this->prts = array_keys($question->prts);
     // TODO: change this to be a list of all *deployed* notes, not just those *used*.
     $qnotes = array();
     foreach ($this->attempts as $qa) {
         $q = $qa->get_question();
         $qnotes[$q->get_question_summary()] = true;
     }
     $this->qnotes = array_keys($qnotes);
     // Compute results.
     list($results, $answernoteresults, $answernoteresultsraw) = $this->input_report();
     list($validresults, $invalidresults) = $this->input_report_separate();
     // Display the results.
     // Overall results.
     $i = 0;
     $list = '';
     $tablehead = array();
     foreach ($this->qnotes as $qnote) {
         $list .= html_writer::tag('li', stack_ouput_castext($qnote));
         $i++;
         $tablehead[] = $i;
     }
     $tablehead[] = format_string(get_string('questionreportingtotal', 'quiz_stack'));
     $tablehead = array_merge(array(''), $tablehead, $tablehead);
     echo html_writer::tag('p', get_string('notesused', 'quiz_stack'));
     echo html_writer::tag('ol', $list);
     // Complete anwernotes.
     $inputstable = new html_table();
     $inputstable->head = $tablehead;
     $data = array();
     foreach ($answernoteresults as $prt => $anotedata) {
         if (count($answernoteresults) > 1) {
             $inputstable->data[] = array(html_writer::tag('b', $this->prts[$prt]));
         }
         $cstats = $this->column_stats($anotedata);
         foreach ($anotedata as $anote => $a) {
             $inputstable->data[] = array_merge(array($anote), $a, array(array_sum($a)), $cstats[$anote]);
         }
     }
     echo html_writer::tag('p', get_string('completenotes', 'quiz_stack'));
     echo html_writer::table($inputstable);
     // Split anwernotes.
     $inputstable = new html_table();
     $inputstable->head = $tablehead;
     foreach ($answernoteresultsraw as $prt => $anotedata) {
         if (count($answernoteresultsraw) > 1) {
             $inputstable->data[] = array(html_writer::tag('b', $this->prts[$prt]));
         }
         $cstats = $this->column_stats($anotedata);
         foreach ($anotedata as $anote => $a) {
             $inputstable->data[] = array_merge(array($anote), $a, array(array_sum($a)), $cstats[$anote]);
         }
     }
     echo html_writer::tag('p', get_string('splitnotes', 'quiz_stack'));
     echo html_writer::table($inputstable);
     // Maxima analysis.
     $maxheader = array();
     $maxheader[] = "STACK input data for the question '" . $question->name . "'";
     $maxheader[] = new moodle_url($this->get_base_url(), array('questionid' => $question->id));
     $maxheader[] = "Data generated: " . date("Y-m-d H:i:s");
     $maximacode = $this->maxima_comment($maxheader);
     $maximacode .= "\ndisplay2d:true\$\nload(\"stackreporting\")\$\n";
     $maximacode .= "stackdata:[]\$\n";
     $variants = array();
     foreach ($this->qnotes as $qnote) {
         $variants[] = '"' . $qnote . '"';
     }
     $inputs = array();
     foreach ($this->inputs as $input) {
         $inputs[] = $input;
     }
     $anymaximadata = false;
     // Results for each question note.
     foreach ($this->qnotes as $qnote) {
         echo html_writer::tag('h2', get_string('variantx', 'quiz_stack') . stack_ouput_castext($qnote));
         $inputstable = new html_table();
         $inputstable->attributes['class'] = 'generaltable stacktestsuite';
         $inputstable->head = array_merge(array(get_string('questionreportingsummary', 'quiz_stack'), '', get_string('questionreportingscore', 'quiz_stack')), $this->prts);
         foreach ($results[$qnote] as $dsummary => $summary) {
             foreach ($summary as $key => $res) {
                 $inputstable->data[] = array_merge(array($dsummary, $res['count'], $res['fraction']), $res['answernotes']);
             }
         }
         echo html_writer::table($inputstable);
         // Separate out inputs and look at validity.
         $validresultsdata = array();
         foreach ($this->inputs as $input) {
             $inputstable = new html_table();
             $inputstable->attributes['class'] = 'generaltable stacktestsuite';
             $inputstable->head = array($input, '', '', '');
             foreach ($validresults[$qnote][$input] as $key => $res) {
                 $validresultsdata[$input][] = $key;
                 $inputstable->data[] = array($key, $res, get_string('inputstatusnamevalid', 'qtype_stack'), '');
                 $inputstable->rowclasses[] = 'pass';
             }
             foreach ($invalidresults[$qnote][$input] as $key => $res) {
                 $inputstable->data[] = array($key, $res[0], get_string('inputstatusnameinvalid', 'qtype_stack'), $res[1]);
                 $inputstable->rowclasses[] = 'fail';
             }
             echo html_writer::table($inputstable);
         }
         // Maxima analysis.
         $maximacode .= "\n/* " . $qnote . ' */ ' . "\n";
         foreach ($this->inputs as $input) {
             if (array_key_exists($input, $validresultsdata)) {
                 $maximacode .= $this->maxima_list_create($validresultsdata[$input], $input);
                 $anymaximadata = true;
             }
         }
         $maximacode .= "stackdata:append(stackdata,[[" . implode(',', $inputs) . "]])\$\n";
     }
     // Maxima analysis at the end.
     if ($anymaximadata) {
         $maximacode .= "\n/* Reset input names */\nkill(" . implode(',', $inputs) . ")\$\n";
         $maximacode .= $this->maxima_list_create($variants, 'variants');
         $maximacode .= $this->maxima_list_create($inputs, 'inputs');
         $maximacode .= "\n/* Perform the analysis. */\nstack_analysis(stackdata)\$\n";
         echo html_writer::tag('h3', get_string('maximacode', 'quiz_stack'));
         echo html_writer::tag('p', get_string('offlineanalysis', 'quiz_stack'));
         $rows = count(explode("\n", $maximacode)) + 2;
         echo html_writer::tag('textarea', $maximacode, array('readonly' => 'readonly', 'wrap' => 'virtual', 'rows' => $rows, 'cols' => '160'));
     }
 }
/**
 * Update the sumgrades field of the results in an offline quiz.
 *
 * @param object $offlinequiz The offlinequiz.
 */
function offlinequiz_update_all_attempt_sumgrades($offlinequiz)
{
    global $DB;
    $dm = new question_engine_data_mapper();
    $timenow = time();
    $sql = "UPDATE {offlinequiz_results}\n               SET timemodified = :timenow,\n                   sumgrades = (\n                                {$dm->sum_usage_marks_subquery('usageid')}\n                               )\n             WHERE offlinequizid = :offlinequizid\n               AND timefinish <> 0";
    $DB->execute($sql, array('timenow' => $timenow, 'offlinequizid' => $offlinequiz->id));
}
Example #18
0
    /**
     * Load the average grade for each question, averaged over particular users.
     * @param array $userids the user ids to average over.
     */
    protected function load_average_question_grades($userids) {
        global $DB;

        $qmfilter = '';
        if ($this->quiz->attempts != 1) {
            $qmfilter = '(' . quiz_report_qm_filter_select($this->quiz, 'quiza') . ') AND ';
        }

        list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u');
        $params['quizid'] = $this->quiz->id;
        $qubaids = new qubaid_join(
                '{quiz_attempts} quiza',
                'quiza.uniqueid',
                "quiza.userid $usql AND quiza.quiz = :quizid",
                $params);

        $dm = new question_engine_data_mapper();
        return $dm->load_average_marks($qubaids, array_keys($this->questions));
    }
Example #19
0
/**
 * Update the sumgrades field of the attempts at a quiz.
 *
 * @param object $quiz a quiz.
 */
function quiz_update_all_attempt_sumgrades($quiz) {
    global $DB;
    $dm = new question_engine_data_mapper();
    $timenow = time();

    $sql = "UPDATE {quiz_attempts}
            SET
                timemodified = :timenow,
                sumgrades = (
                    {$dm->sum_usage_marks_subquery('uniqueid')}
                )
            WHERE quiz = :quizid AND state = :finishedstate";
    $DB->execute($sql, array('timenow' => $timenow, 'quizid' => $quiz->id,
            'finishedstate' => quiz_attempt::FINISHED));
}
Example #20
0
 /**
  * Write all the changes we have recorded to the database.
  * @param question_engine_data_mapper $dm the mapper to use to update the database.
  */
 public function save(question_engine_data_mapper $dm)
 {
     $dm->delete_steps(array_keys($this->stepsdeleted), $this->quba->get_owning_context());
     // Initially an array of array of question_attempt_step_objects.
     // Built as a nested array for efficiency, then flattened.
     $stepdata = array();
     foreach ($this->stepsmodified as $stepinfo) {
         list($step, $questionattemptid, $seq) = $stepinfo;
         $stepdata[] = $dm->update_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context());
     }
     foreach ($this->stepsadded as $stepinfo) {
         list($step, $questionattemptid, $seq) = $stepinfo;
         $stepdata[] = $dm->insert_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context());
     }
     foreach ($this->attemptsadded as $qa) {
         $stepdata[] = $dm->insert_question_attempt($qa, $this->quba->get_owning_context());
     }
     foreach ($this->attemptsmodified as $qa) {
         $dm->update_question_attempt($qa);
     }
     if ($this->modified) {
         $dm->update_questions_usage_by_activity($this->quba);
     }
     if (!$stepdata) {
         return;
     }
     $dm->insert_all_step_data(call_user_func_array('array_merge', $stepdata));
 }
/**
 * Deletes question and all associated data from the database
 *
 * It will not delete a question if it is used by an activity module
 * @param object $question  The question being deleted
 */
function question_delete_question($questionid)
{
    global $DB;
    $question = $DB->get_record_sql('
            SELECT q.*, qc.contextid
            FROM {question} q
            JOIN {question_categories} qc ON qc.id = q.category
            WHERE q.id = ?', array($questionid));
    if (!$question) {
        // In some situations, for example if this was a child of a
        // Cloze question that was previously deleted, the question may already
        // have gone. In this case, just do nothing.
        return;
    }
    // Do not delete a question if it is used by an activity module
    if (questions_in_use(array($questionid))) {
        return;
    }
    // Check permissions.
    question_require_capability_on($question, 'edit');
    $dm = new question_engine_data_mapper();
    $dm->delete_previews($questionid);
    // delete questiontype-specific data
    question_bank::get_qtype($question->qtype, false)->delete_question($questionid, $question->contextid);
    // Now recursively delete all child questions
    if ($children = $DB->get_records('question', array('parent' => $questionid), '', 'id, qtype')) {
        foreach ($children as $child) {
            if ($child->id != $questionid) {
                question_delete_question($child->id);
            }
        }
    }
    // Finally delete the question record itself
    $DB->delete_records('question', array('id' => $questionid));
    question_bank::notify_question_edited($questionid);
}
Example #22
0
 /**
  * Analyse all the response data for for all the specified attempts at this question.
  *
  * @param \qubaid_condition $qubaids which attempts to consider.
  * @param string $whichtries         which tries to analyse. Will be one of
  *                                   \question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES.
  * @return analysis_for_question
  */
 public function calculate($qubaids, $whichtries = \question_attempt::LAST_TRY)
 {
     // Load data.
     $dm = new \question_engine_data_mapper();
     $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
     // Analyse it.
     foreach ($questionattempts as $qa) {
         $responseparts = $qa->classify_response($whichtries);
         if ($this->breakdownbyvariant) {
             $this->analysis->count_response_parts($qa->get_variant(), $responseparts);
         } else {
             $this->analysis->count_response_parts(1, $responseparts);
         }
     }
     $this->analysis->cache($qubaids, $whichtries, $this->questiondata->id);
     return $this->analysis;
 }
Example #23
0
    /**
     * Get a list of usage ids where the question with slot $slot, and optionally
     * also with question id $questionid, is in summary state $summarystate. Also
     * return the total count of such states.
     *
     * Only a subset of the ids can be returned by using $orderby, $limitfrom and
     * $limitnum. A special value 'random' can be passed as $orderby, in which case
     * $limitfrom is ignored.
     *
     * @param int $slot The slot for the questions you want to konw about.
     * @param int $questionid (optional) Only return attempts that were of this specific question.
     * @param string $summarystate 'all', 'needsgrading', 'autograded' or 'manuallygraded'.
     * @param string $orderby 'random', 'date', 'student' or 'idnumber'.
     * @param int $page implements paging of the results.
     *      Ignored if $orderby = random or $pagesize is null.
     * @param int $pagesize implements paging of the results. null = all.
     */
    protected function get_usage_ids_where_question_in_state($summarystate, $slot,
            $questionid = null, $orderby = 'random', $page = 0, $pagesize = null) {
        global $CFG, $DB;
        $dm = new question_engine_data_mapper();

        if ($pagesize && $orderby != 'random') {
            $limitfrom = $page * $pagesize;
        } else {
            $limitfrom = 0;
        }

        $qubaids = $this->get_qubaids_condition();

        $params = array();
        if ($orderby == 'date') {
            list($statetest, $params) = $dm->in_summary_state_test(
                    'manuallygraded', false, 'mangrstate');
            $orderby = "(
                    SELECT MAX(sortqas.timecreated)
                    FROM {question_attempt_steps} sortqas
                    WHERE sortqas.questionattemptid = qa.id
                        AND sortqas.state $statetest
                    )";
        } else if ($orderby == 'student' || $orderby == 'idnumber') {
            $qubaids->from .= " JOIN {user} u ON quiza.userid = u.id ";
            if ($orderby == 'student') {
                $orderby = $DB->sql_fullname('u.firstname', 'u.lastname');
            }
        }

        return $dm->load_questions_usages_where_question_in_state($qubaids, $summarystate,
                $slot, $questionid, $orderby, $params, $limitfrom, $pagesize);
    }
Example #24
0
 /**
  * Analyse all the response data for for all the specified attempts at
  * this question.
  * @param \qubaid_condition $qubaids which attempts to consider.
  * @return analysis_for_question
  */
 public function calculate($qubaids)
 {
     // Load data.
     $dm = new \question_engine_data_mapper();
     $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
     // Analyse it.
     foreach ($questionattempts as $qa) {
         $responseparts = $qa->classify_response();
         $this->analysis->count_response_parts($responseparts);
     }
     $this->analysis->cache($qubaids, $this->questiondata->id);
     return $this->analysis;
 }