/** * Get a list of the PRT notes that should be present for a given PRT. * @param string $prtname the name of a PRT. * @return array list of nodes that should be present in the form definitino for this PRT. */ protected function get_prt_graph($prtname) { if (array_key_exists($prtname, $this->prtgraph)) { return $this->prtgraph[$prtname]; } // If the form has been submitted and is being redisplayed, and this is // an existing PRT, base things on the submitted data. $submitted = optional_param_array($prtname . 'truenextnode', null, PARAM_RAW); if ($submitted) { $truescoremode = optional_param_array($prtname . 'truescoremode', null, PARAM_RAW); $truescore = optional_param_array($prtname . 'truescore', null, PARAM_RAW); $falsenextnode = optional_param_array($prtname . 'falsenextnode', null, PARAM_RAW); $falsescoremode = optional_param_array($prtname . 'falsescoremode', null, PARAM_RAW); $falsescore = optional_param_array($prtname . 'falsescore', null, PARAM_RAW); $graph = new stack_abstract_graph(); $deletednode = null; $lastkey = -1; foreach ($submitted as $key => $truenextnode) { if (optional_param($prtname . 'nodedelete' . $key, false, PARAM_BOOL)) { // Slightly odd to register the button here, especially since // now this node has been deleted, this button will not exist, // but anyway this works, and in necessary to stop the form // from being submitted. $this->_form->registerNoSubmitButton($prtname . 'nodedelete' . $key); // For deleted nodes, we add them to the tree anyway, and // then remove them again below. We have to do it that way // because we also need to delete links that point to the // deleted node. $deletednode = $key; } if ($truenextnode == -1 || !array_key_exists($truenextnode, $submitted)) { $left = null; } else { $left = $truenextnode + 1; } if ($falsenextnode[$key] == -1 || !array_key_exists($falsenextnode[$key], $submitted)) { $right = null; } else { $right = $falsenextnode[$key] + 1; } $graph->add_node($key + 1, $left, $right, $truescoremode[$key] . round($truescore[$key], 2), $falsescoremode[$key] . round($falsescore[$key], 2), '#fgroup_id_' . $prtname . 'node_' . $key); $lastkey = max($lastkey, $key); } if (optional_param($prtname . 'nodeadd', false, PARAM_BOOL)) { $graph->add_node($lastkey + 2, null, null, '+0', '-0', '#fgroup_id_' . $prtname . 'node_' . $lastkey + 1); } if (!is_null($deletednode)) { $graph->remove_node($deletednode + 1); } $graph->layout(); $this->prtgraph[$prtname] = $graph; return $graph; } // Otherwise, if an existing question is being edited, and this is an // existing PRT, base things on the existing question definition. if (!empty($this->question->prts[$prtname]->nodes)) { $graph = new stack_abstract_graph(); foreach ($this->question->prts[$prtname]->nodes as $node) { if ($node->truenextnode == -1) { $left = null; } else { $left = $node->truenextnode + 1; } if ($node->falsenextnode == -1) { $right = null; } else { $right = $node->falsenextnode + 1; } $graph->add_node($node->nodename + 1, $left, $right, $node->truescoremode . round($node->truescore, 2), $node->falsescoremode . round($node->falsescore, 2), '#fgroup_id_' . $prtname . 'node_' . $node->nodename); } $graph->layout(); $this->prtgraph[$prtname] = $graph; return $graph; } // Otherwise, it is a new PRT. Just one node. $graph = new stack_abstract_graph(); $graph->add_node('1', null, null, '=1', '=0', '#fgroup_id_' . $prtname . 'node_0'); $graph->layout(); $this->prtgraph[$prtname] = $graph; return $graph; }
/** * This graph has a link to a non-existent node. We verify that throws an exception. */ public function test_get_suggested_node_names() { $graph = new stack_abstract_graph(); $graph->add_node(1, 2, 3); $graph->add_node(2, 7, null); $graph->add_node(3, 2, 4); $graph->add_node(4, 5, 6); $graph->add_node(5, null, 9); $graph->add_node(6, null, null); $graph->add_node(7, null, null); $graph->add_node(9, null, null); $graph->layout(); $newnames = $graph->get_suggested_node_names(); $this->assertEquals(array(1 => 1, 3 => 2, 2 => 3, 7 => 4, 4 => 5, 5 => 6, 9 => 7, 6 => 8), $newnames); }
public function save_question_options($fromform) { global $DB; $context = $fromform->context; parent::save_question_options($fromform); $options = $DB->get_record('qtype_stack_options', array('questionid' => $fromform->id)); if (!$options) { $options = new stdClass(); $options->questionid = $fromform->id; $options->questionvariables = ''; $options->specificfeedback = ''; $options->prtcorrect = ''; $options->prtpartiallycorrect = ''; $options->prtincorrect = ''; $options->id = $DB->insert_record('qtype_stack_options', $options); } $options->questionvariables = $fromform->questionvariables; $options->specificfeedback = $this->import_or_save_files($fromform->specificfeedback, $context, 'qtype_stack', 'specificfeedback', $fromform->id); $options->specificfeedbackformat = $fromform->specificfeedback['format']; $options->questionnote = $fromform->questionnote; $options->questionsimplify = $fromform->questionsimplify; $options->assumepositive = $fromform->assumepositive; $options->prtcorrect = $this->import_or_save_files($fromform->prtcorrect, $context, 'qtype_stack', 'prtcorrect', $fromform->id); $options->prtcorrectformat = $fromform->prtcorrect['format']; $options->prtpartiallycorrect = $this->import_or_save_files($fromform->prtpartiallycorrect, $context, 'qtype_stack', 'prtpartiallycorrect', $fromform->id); $options->prtpartiallycorrectformat = $fromform->prtpartiallycorrect['format']; $options->prtincorrect = $this->import_or_save_files($fromform->prtincorrect, $context, 'qtype_stack', 'prtincorrect', $fromform->id); $options->prtincorrectformat = $fromform->prtincorrect['format']; $options->multiplicationsign = $fromform->multiplicationsign; $options->sqrtsign = $fromform->sqrtsign; $options->complexno = $fromform->complexno; $options->inversetrig = $fromform->inversetrig; $options->matrixparens = $fromform->matrixparens; $options->variantsselectionseed = $fromform->variantsselectionseed; $DB->update_record('qtype_stack_options', $options); $inputnames = stack_utils::extract_placeholders($fromform->questiontext, 'input'); $inputs = $DB->get_records('qtype_stack_inputs', array('questionid' => $fromform->id), '', 'name, id, questionid'); $questionhasinputs = false; foreach ($inputnames as $inputname) { if (array_key_exists($inputname, $inputs)) { $input = $inputs[$inputname]; unset($inputs[$inputname]); } else { $input = new stdClass(); $input->questionid = $fromform->id; $input->name = $inputname; $input->options = ''; $input->id = $DB->insert_record('qtype_stack_inputs', $input); } $input->type = $fromform->{$inputname . 'type'}; $input->tans = $fromform->{$inputname . 'modelans'}; $input->boxsize = $fromform->{$inputname . 'boxsize'}; $input->strictsyntax = $fromform->{$inputname . 'strictsyntax'}; $input->insertstars = $fromform->{$inputname . 'insertstars'}; $input->syntaxhint = $fromform->{$inputname . 'syntaxhint'}; $input->forbidwords = $fromform->{$inputname . 'forbidwords'}; $input->allowwords = $fromform->{$inputname . 'allowwords'}; $input->forbidfloat = $fromform->{$inputname . 'forbidfloat'}; $input->requirelowestterms = $fromform->{$inputname . 'requirelowestterms'}; $input->checkanswertype = $fromform->{$inputname . 'checkanswertype'}; $input->mustverify = $fromform->{$inputname . 'mustverify'}; $input->showvalidation = $fromform->{$inputname . 'showvalidation'}; $input->options = $fromform->{$inputname . 'options'}; $questionhasinputs = true; $DB->update_record('qtype_stack_inputs', $input); } if ($inputs) { list($test, $params) = $DB->get_in_or_equal(array_keys($inputs)); $params[] = $fromform->id; $DB->delete_records_select('qtype_stack_inputs', 'name ' . $test . ' AND questionid = ?', $params); } if (!$questionhasinputs) { // A question with no inputs is an information item. $DB->set_field('question', 'length', 0, array('id' => $fromform->id)); } $prtnames = stack_utils::extract_placeholders($fromform->questiontext . $options->specificfeedback, 'feedback'); $prts = $DB->get_records('qtype_stack_prts', array('questionid' => $fromform->id), '', 'name, id, questionid'); foreach ($prtnames as $prtname) { if (array_key_exists($prtname, $prts)) { $prt = $prts[$prtname]; unset($prts[$prtname]); } else { $prt = new stdClass(); $prt->questionid = $fromform->id; $prt->name = $prtname; $prt->feedbackvariables = ''; $prt->firstnodename = 0; $prt->id = $DB->insert_record('qtype_stack_prts', $prt); } // Find the root node of the PRT. // Otherwise, if an existing question is being edited, and this is an // existing PRT, base things on the existing question definition. $graph = new stack_abstract_graph(); foreach ($fromform->{$prtname . 'answertest'} as $nodename => $notused) { $truenextnode = $fromform->{$prtname . 'truenextnode'}[$nodename]; $falsenextnode = $fromform->{$prtname . 'falsenextnode'}[$nodename]; if ($truenextnode == -1) { $left = null; } else { $left = $truenextnode + 1; } if ($falsenextnode == -1) { $right = null; } else { $right = $falsenextnode + 1; } $graph->add_node($nodename + 1, $left, $right); } $graph->layout(); $roots = $graph->get_roots(); if (count($roots) != 1 || $graph->get_broken_cycles()) { throw new coding_exception('The PRT ' . $prtname . ' is malformed.'); } reset($roots); $firstnode = key($roots) - 1; $prt->value = $fromform->{$prtname . 'value'}; $prt->autosimplify = $fromform->{$prtname . 'autosimplify'}; $prt->feedbackvariables = $fromform->{$prtname . 'feedbackvariables'}; $prt->firstnodename = $firstnode; $DB->update_record('qtype_stack_prts', $prt); $nodes = $DB->get_records('qtype_stack_prt_nodes', array('questionid' => $fromform->id, 'prtname' => $prtname), '', 'nodename, id, questionid, prtname'); foreach ($fromform->{$prtname . 'answertest'} as $nodename => $notused) { if (array_key_exists($nodename, $nodes)) { $node = $nodes[$nodename]; unset($nodes[$nodename]); } else { $node = new stdClass(); $node->questionid = $fromform->id; $node->prtname = $prtname; $node->nodename = $nodename; $node->truefeedback = ''; $node->falsefeedback = ''; $node->id = $DB->insert_record('qtype_stack_prt_nodes', $node); } $node->answertest = $fromform->{$prtname . 'answertest'}[$nodename]; $node->sans = $fromform->{$prtname . 'sans'}[$nodename]; $node->tans = $fromform->{$prtname . 'tans'}[$nodename]; $node->testoptions = $fromform->{$prtname . 'testoptions'}[$nodename]; $node->quiet = $fromform->{$prtname . 'quiet'}[$nodename]; $node->truescoremode = $fromform->{$prtname . 'truescoremode'}[$nodename]; $node->truescore = $fromform->{$prtname . 'truescore'}[$nodename]; $node->truepenalty = $fromform->{$prtname . 'truepenalty'}[$nodename]; $node->truenextnode = $fromform->{$prtname . 'truenextnode'}[$nodename]; $node->trueanswernote = $fromform->{$prtname . 'trueanswernote'}[$nodename]; $node->truefeedback = $this->import_or_save_files($fromform->{$prtname . 'truefeedback'}[$nodename], $context, 'qtype_stack', 'prtnodetruefeedback', $node->id); $node->truefeedbackformat = $fromform->{$prtname . 'truefeedback'}[$nodename]['format']; $node->falsescoremode = $fromform->{$prtname . 'falsescoremode'}[$nodename]; $node->falsescore = $fromform->{$prtname . 'falsescore'}[$nodename]; $node->falsepenalty = $fromform->{$prtname . 'falsepenalty'}[$nodename]; $node->falsenextnode = $fromform->{$prtname . 'falsenextnode'}[$nodename]; $node->falseanswernote = $fromform->{$prtname . 'falseanswernote'}[$nodename]; $node->falsefeedback = $this->import_or_save_files($fromform->{$prtname . 'falsefeedback'}[$nodename], $context, 'qtype_stack', 'prtnodefalsefeedback', $node->id); $node->falsefeedbackformat = $fromform->{$prtname . 'falsefeedback'}[$nodename]['format']; if ('' === $node->truepenalty) { $node->truepenalty = null; } if ('' === $node->falsepenalty) { $node->falsepenalty = null; } $DB->update_record('qtype_stack_prt_nodes', $node); } if ($nodes) { list($test, $params) = $DB->get_in_or_equal(array_keys($nodes)); $params[] = $fromform->id; $params[] = $prt->name; $DB->delete_records_select('qtype_stack_prt_nodes', 'nodename ' . $test . ' AND questionid = ? AND prtname = ?', $params); } } if ($prts) { list($test, $params) = $DB->get_in_or_equal(array_keys($prts)); $params[] = $fromform->id; $DB->delete_records_select('qtype_stack_prt_nodes', 'prtname ' . $test . ' AND questionid = ?', $params); $DB->delete_records_select('qtype_stack_prts', 'name ' . $test . ' AND questionid = ?', $params); } $this->save_hints($fromform); if (isset($fromform->deployedseeds)) { $DB->delete_records('qtype_stack_deployed_seeds', array('questionid' => $fromform->id)); foreach ($fromform->deployedseeds as $deployedseed) { $record = new stdClass(); $record->questionid = $fromform->id; $record->seed = $deployedseed; $DB->insert_record('qtype_stack_deployed_seeds', $record, false); } } // This is a bit of a hack. If doing 'Make a copy' when saving the // editing form, then detect that here, and try to copy the question // tests from the original question. if (!isset($fromform->testcases) && !empty($fromform->makecopy)) { $oldquestionid = optional_param('id', 0, PARAM_INT); if ($oldquestionid) { $fromform->testcases = $this->load_question_tests($oldquestionid); } } if (isset($fromform->testcases)) { // If the data includes the defintion of the question tests that there // should be (i.e. when doing import) then replace the existing set // of tests with the new one. $this->save_question_tests($fromform->id, $fromform->testcases); } // Irrespective of what else has happened, ensure there is no garbage // in the database, for example if we delete a PRT, remove the expected // values for that PRT while leaving the rest of the testcases alone. list($nametest, $params) = $DB->get_in_or_equal($inputnames, SQL_PARAMS_NAMED, 'input', false, null); $params['questionid'] = $fromform->id; $DB->delete_records_select('qtype_stack_qtest_inputs', 'questionid = :questionid AND inputname ' . $nametest, $params); list($nametest, $params) = $DB->get_in_or_equal($prtnames, SQL_PARAMS_NAMED, 'prt', false, null); $params['questionid'] = $fromform->id; $DB->delete_records_select('qtype_stack_qtest_expected', 'questionid = :questionid AND prtname ' . $nametest, $params); }
/** * Get a stack_abstract_graph represemtatopm of a PRT. * @return stack_abstract_graph. */ protected function get_prt_graph($prt) { $graph = new stack_abstract_graph(); foreach ($prt->get_nodes_summary() as $nodekey => $summary) { if ($summary->truenextnode == -1) { $left = null; } else { $left = $summary->truenextnode + 1; } if ($summary->falsenextnode == -1) { $right = null; } else { $right = $summary->falsenextnode + 1; } $graph->add_node($nodekey + 1, $left, $right, $summary->truescoremode . round($summary->truescore, 2), $summary->falsescoremode . round($summary->falsescore, 2)); } $graph->layout(); return $graph; }
list($left, $right) = $branches; if ($left == -1) { $left = null; } else { $left += 1; } if ($right == -1) { $right = null; } else { $right += 1; } $tree->add_node($node + 1, $left, $right); } reset($uniquetree); echo $OUTPUT->heading('Tree used ' . $count . ' times'); if ($count < 10) { echo '<p>' . implode('<br .>', $qnamesused[$key]) . '</p>'; } try { $tree->layout(); echo stack_abstract_graph_svg_renderer::render($tree, 'real' . $i++); } catch (Exception $e) { print_object($tree); foreach ($trees as $name => $tree) { if (json_encode($tree) == $key) { print_object($name); } } } } echo $OUTPUT->footer();
/** * When restoring old data, that does not have the essay options information * in the XML, supply defaults. */ protected function after_execute_question() { global $DB; $prtswithoutfirstnode = $DB->get_records('qtype_stack_prts', array('firstnodename' => -1), '', 'id, questionid, name'); foreach ($prtswithoutfirstnode as $prt) { $nodes = $DB->get_records('qtype_stack_prt_nodes', array('questionid' => $prt->questionid, 'prtname' => $prt->name), '', 'nodename, truenextnode, falsenextnode'); // Find the root node of the PRT. // Otherwise, if an existing question is being edited, and this is an // existing PRT, base things on the existing question definition. $graph = new stack_abstract_graph(); foreach ($nodes as $node) { if ($node->truenextnode == -1) { $left = null; } else { $left = $node->truenextnode + 1; } if ($node->falsenextnode == -1) { $right = null; } else { $right = $node->falsenextnode + 1; } $graph->add_node($node->nodename + 1, $left, $right); } $graph->layout(); $roots = $graph->get_roots(); if (count($roots) != 1 || $graph->get_broken_cycles()) { $questions = $DB->get_records('question', array('id' => $prt->questionid), '', 'name'); $qnames = array(); foreach ($questions as $q) { $qnames[] = $q->name; } if (count($roots) != 1) { $err = 'abnormal root count: ' . count($roots) . '(<>1)'; } else { $err = 'broken cycles: ' . implode('.', $graph->get_broken_cycles()); } throw new coding_exception('The PRT named "' . $prt->name . '" is malformed in question id ' . $prt->questionid . ', question named "' . implode(', ', $qnames) . '". Error reported: ' . $err); } reset($roots); $firstnode = key($roots) - 1; $DB->set_field('qtype_stack_prts', 'firstnodename', $firstnode, array('id' => $prt->id)); } }