public function setUp() { parent::setUp(); stack_utils::clear_config_cache(); self::setup_test_maxima_connection($this); $this->resetAfterTest(); }
public function test_maths_rendering() { if (!stack_maths_output_maths::filter_is_installed()) { $this->markTestSkipped('The OU maths filter is not installed.'); } if (!defined('FILTER_MATHS_TEST_SERVICE_URL_BASE')) { $this->markTestSkipped('To run the OU maths filter output tests, ' . 'you must define FILTER_MATHS_TEST_SERVICE_URL_BASE in config.php.'); } $this->resetAfterTest(); set_config('mathsdisplay', 'maths', 'qtype_stack'); set_config('texservice', FILTER_MATHS_TEST_SERVICE_URL_BASE . 'tex', 'filter_maths'); set_config('imageservice', FILTER_MATHS_TEST_SERVICE_URL_BASE . 'imagetex', 'filter_maths'); set_config('englishservice', FILTER_MATHS_TEST_SERVICE_URL_BASE . 'english', 'filter_maths'); stack_utils::clear_config_cache(); filter_set_global_state('mathjaxloader', TEXTFILTER_DISABLED); // Test language string. $this->assertRegExp('~^Your answer needs to be a single fraction of the form <a .*alt="a over b".*</a>\\. $~', stack_string('ATSingleFrac_part')); // Test docs - make sure maths inside <code> is not rendered. $this->assertRegExp('~^<p><code>\\\\\\(x\\^2\\\\\\)</code> gives <a .*alt="x squared".*</a>\\.</p>\\n$~', stack_docs_render_markdown('<code>\\(x^2\\)</code> gives \\(x^2\\).', '')); // Test docs - make sure maths inside <textarea> is not rendered. $this->assertRegExp('~^<p>\\n' . 'Differentiate \\\\\\\\\\[x\\^2 \\+ y\\^2\\\\\\\\\\] with respect to \\\\\\\\\\(x\\\\\\\\\\).</p>\\n$~', stack_docs_render_markdown('<textarea readonly="readonly" rows="3" cols="50">' . "\n" . 'Differentiate \\[x^2 + y^2\\] with respect to \\(x\\).</textarea>', '')); // Test CAS text with inline maths. $this->assertEquals('What is <tex mode="inline">x^2</tex>?', stack_maths::process_display_castext('What is \\(x^2\\)?')); // Test CAS text with display maths. $this->assertEquals('What is <span class="displayequation"><tex mode="display">x^2</tex></span>?', stack_maths::process_display_castext('What is \\[x^2\\]?')); // Test with replacedollars. set_config('replacedollars', '1', 'qtype_stack'); stack_utils::clear_config_cache(); $this->assertEquals('What is <tex mode="inline">x^2</tex> or ' . '<span class="displayequation"><tex mode="display">x^2</tex></span>?', stack_maths::process_display_castext('What is $x^2$ or $$x^2$$?')); stack_utils::clear_config_cache(); }
/** * Display the STACK documentation index for a particular path. * @param string $dir directory. * @param string $relpath relative path to that directory. */ function stack_docs_index($dir, $relpath = '') { // Write a list describing the directory structure, recursive, discriminates for .md files. $exclude = array('index.md', 'Site_map.md'); if (!is_dir($dir)) { return ''; } $items = array(); foreach (glob($dir . '/*') as $filepath) { $filename = basename($filepath); if (substr($filename, 0, 1) === '.' || in_array($filename, $exclude)) { continue; } $title = stack_docs_title_from_filename($filename); if (is_dir($filepath)) { $items[$title] = "<li><a href=\"{$relpath}/{$filename}/\">" . $title . "</a>" . stack_docs_index($filepath, "{$relpath}/{$filename}") . '</li>'; } else { if (substr($filename, -2) === 'md') { $items[$title] = "<li><a href=\"{$relpath}/{$filename}\">" . $title . '</a></li>'; } } } if (empty($items)) { return ''; } stack_utils::sort_array_by_key($items); return '<ul class="dir">' . implode('', $items) . '</ul>'; }
public function test_maths_output_mathsjax() { filter_set_global_state('mathjaxloader', TEXTFILTER_DISABLED); // MathJax output is the default. $this->assertEquals('Your answer needs to be a single fraction of the form \\( {a}\\over{b} \\). ', stack_string('ATSingleFrac_part')); $this->assertEquals("<p><code>\\(x^2\\)</code> gives \\(x^2\\).</p>\n", stack_docs_render_markdown('`\\(x^2\\)` gives \\(x^2\\).', '')); $this->assertEquals('What is \\(x^2\\)?', stack_maths::process_display_castext('What is \\(x^2\\)?')); $this->resetAfterTest(); set_config('replacedollars', '1', 'qtype_stack'); stack_utils::clear_config_cache(); $this->assertEquals('What is \\(x^2\\) or \\[x^2\\]?', stack_maths::process_display_castext('What is $x^2$ or $$x^2$$?')); stack_utils::clear_config_cache(); }
public function set_site_defaults() { $stackconfig = stack_utils::get_config(); // Display option does not match up to $stackconfig->mathsdisplay). $this->set_option('multiplicationsign', $stackconfig->multiplicationsign); $this->set_option('complexno', $stackconfig->complexno); $this->set_option('inversetrig', $stackconfig->inversetrig); $this->set_option('matrixparens', $stackconfig->matrixparens); $this->set_option('floats', (bool) $stackconfig->inputforbidfloat); $this->set_option('sqrtsign', (bool) $stackconfig->sqrtsign); $this->set_option('simplify', (bool) $stackconfig->questionsimplify); $this->set_option('assumepos', (bool) $stackconfig->assumepositive); return true; }
protected function get_choices() { if (empty($this->parameters['ddl_values'])) { return array(); } $values = stack_utils::list_to_array('[' . trim($this->parameters['ddl_values']) . ']', false); if (empty($values)) { return array(); } $choices = array('' => stack_string('notanswered')); foreach ($values as $value) { $choices[$value] = $value; } return $choices; }
public function formulation_and_controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); $response = $qa->get_last_qt_data(); $questiontext = $question->questiontextinstantiated; // Replace inputs. $inputstovaldiate = array(); $qaid = null; foreach ($question->inputs as $name => $input) { $fieldname = $qa->get_qt_field_name($name); $state = $question->get_input_state($name, $response); $questiontext = str_replace("[[input:{$name}]]", $input->render($state, $fieldname, $options->readonly), $questiontext); $feedback = $this->input_validation($fieldname . '_val', $input->render_validation($state, $fieldname)); $questiontext = str_replace("[[validation:{$name}]]", $feedback, $questiontext); $qaid = $qa->get_database_id(); if ($input->requires_validation()) { $inputstovaldiate[] = $name; } } // Replace PRTs. foreach ($question->prts as $index => $prt) { $feedback = ''; if ($options->feedback) { $feedback = $this->prt_feedback($index, $response, $qa, $options, true); } else { if (in_array($qa->get_behaviour_name(), array('interactivecountback', 'adaptivemulipart'))) { // The behaviour name test here is a hack. The trouble is that interactive // behaviour or adaptivemulipart does not show feedback if the input // is invalid, but we want to show the CAS errors from the PRT. $result = $question->get_prt_result($index, $response, $qa->get_state()->is_finished()); $feedback = html_writer::nonempty_tag('div', $result->errors, array('class' => 'stackprtfeedback stackprtfeedback-' . $name)); } } $questiontext = str_replace("[[feedback:{$index}]]", $feedback, $questiontext); } // Now format the questiontext. This should be done after the subsitutions of inputs and PRTs. $questiontext = $question->format_text(stack_maths::process_display_castext($questiontext, $this), $question->questiontextformat, $qa, 'question', 'questiontext', $question->id); // Initialise automatic validation, if enabled. if ($qaid && stack_utils::get_config()->ajaxvalidation) { $this->page->requires->yui_module('moodle-qtype_stack-input', 'M.qtype_stack.init_inputs', array($inputstovaldiate, $qaid, $qa->get_field_prefix())); } $result = ''; $result .= $this->question_tests_link($question, $options) . $questiontext; if ($qa->get_state() == question_state::$invalid) { $result .= html_writer::nonempty_tag('div', $question->get_validation_error($response), array('class' => 'validationerror')); } return $result; }
private function validate() { if (empty($this->raw) or '' == trim($this->raw)) { $this->valid = true; return true; } // CAS keyval may not contain @ or $. if (strpos($this->raw, '@') !== false || strpos($this->raw, '$') !== false) { $this->errors = stack_string('illegalcaschars'); $this->valid = false; return false; } // Subtle one: must protect things inside strings before we explode. $str = $this->raw; $strings = stack_utils::all_substring_strings($str); foreach ($strings as $key => $string) { $str = str_replace('"' . $string . '"', '[STR:' . $key . ']', $str); } $str = str_replace("\n", ';', $str); $str = stack_utils::remove_comments($str); $str = str_replace(';', "\n", $str); $kvarray = explode("\n", $str); foreach ($strings as $key => $string) { foreach ($kvarray as $kkey => $kstr) { $kvarray[$kkey] = str_replace('[STR:' . $key . ']', '"' . $string . '"', $kstr); } } // 23/4/12 - significant changes to the way keyvals are interpreted. Use Maxima assignmentsm i.e. x:2. $errors = ''; $valid = true; $vars = array(); foreach ($kvarray as $kvs) { $kvs = trim($kvs); if ('' != $kvs) { $cs = new stack_cas_casstring($kvs); $cs->get_valid($this->security, $this->syntax, $this->insertstars); $vars[] = $cs; } } $this->session->add_vars($vars); $this->valid = $this->session->get_valid(); $this->errors = $this->session->get_errors(); }
public function test_tex_rendering() { $this->resetAfterTest(); set_config('mathsdisplay', 'tex', 'qtype_stack'); stack_utils::clear_config_cache(); filter_set_global_state('mathjaxloader', TEXTFILTER_DISABLED); // Test language string. // The <span class="MathJax_Preview"> bit is something that got added in // Moodle 2.8, so match it optionally. $this->assertRegExp('~^Your answer needs to be a single fraction of the form ' . '(<span class="MathJax_Preview">)?<a .*alt=" \\{a\\}\\\\over\\{b\\} ".*</(a|script)> \\. ~', stack_string('ATSingleFrac_part')); // Test docs - make sure maths inside <code> is not rendered. $this->assertRegExp('~^<p><code>\\\\\\(x\\^2\\\\\\)</code> gives (<span class="MathJax_Preview">)?<a .*alt="x\\^2".*</(a|script)> \\.</p>\\n$~', stack_docs_render_markdown('<code>\\(x^2\\)</code> gives \\(x^2\\).', '')); // Test docs - make sure maths inside <textarea> is not rendered. $this->assertRegExp('~^<p>\\n' . 'Differentiate \\\\\\\\\\[x\\^2 \\+ y\\^2\\\\\\\\\\] with respect to \\\\\\\\\\(x\\\\\\\\\\).</p>\\n$~', stack_docs_render_markdown('<textarea readonly="readonly" rows="3" cols="50">' . "\n" . 'Differentiate \\[x^2 + y^2\\] with respect to \\(x\\).</textarea>', '')); // Test CAS text with inline maths. $this->assertEquals('What is \\[x^2\\]?', stack_maths::process_display_castext('What is \\(x^2\\)?')); // Test CAS text with display maths. $this->assertEquals('What is <span class="displayequation">\\[\\displaystyle x^2\\]</span>?', stack_maths::process_display_castext('What is \\[x^2\\]?')); // Test with replacedollars. set_config('replacedollars', '1', 'qtype_stack'); stack_utils::clear_config_cache(); $this->assertEquals('What is \\[x^2\\] or <span class="displayequation">\\[\\displaystyle x^2\\]</span>?', stack_maths::process_display_castext('What is $x^2$ or $$x^2$$?')); stack_utils::clear_config_cache(); }
/** * We need to make sure the inputs are displayed in the order in which they * occur in the question text. This is not necessarily the order in which they * are listed in the array $this->inputs. */ public function format_correct_response($qa) { $feedback = ''; $inputs = stack_utils::extract_placeholders($this->questiontextinstantiated, 'input'); foreach ($inputs as $name) { $input = $this->inputs[$name]; $feedback .= html_writer::tag('p', $input->get_teacher_answer_display($this->session->get_value_key($name), $this->session->get_display_key($name))); } return stack_ouput_castext($feedback); }
/** * @return array input type internal name => display name. */ public static function get_available_type_choices() { $types = self::get_available_types(); $choices = array(); foreach ($types as $type => $notused) { $choices[$type] = stack_string('inputtype' . $type); } stack_utils::sort_array($choices); return $choices; }
// Check permissions. require_login(); $context = context_system::instance(); require_capability('moodle/site:config', $context); // Set up page. $PAGE->set_context($context); $PAGE->set_url('/question/type/stack/healthcheck.php'); $title = stack_string('healthcheck'); $PAGE->set_title($title); // Clear the cache if requested. if (data_submitted() && optional_param('clearcache', false, PARAM_BOOL)) { require_sesskey(); stack_cas_connection_db_cache::clear_cache($DB); redirect($PAGE->url); } $config = stack_utils::get_config(); // Start output. echo $OUTPUT->header(); echo $OUTPUT->heading($title); // Summary // This array holds summary info, for a table at the end of the pager. $summary = array(); // LaTeX. echo $OUTPUT->heading(stack_string('healthchecklatex'), 3); echo html_writer::tag('p', stack_string('healthcheckmathsdisplaymethod', stack_maths::configured_output_name())); echo html_writer::tag('p', stack_string('healthchecklatexintro')); echo html_writer::tag('dt', stack_string('texdisplaystyle')); echo html_writer::tag('dd', format_text(stack_string('healthchecksampledisplaytex'))); echo html_writer::tag('dt', stack_string('texinlinestyle')); echo html_writer::tag('dd', format_text(stack_string('healthchecksampleinlinetex'))); if ($config->mathsdisplay === 'mathjax') {
public static function stackmaxima_auto_maxima_optimise($genuinedebug) { global $CFG; self::ensure_config_loaded(); $imagename = stack_utils::convert_slash_paths($CFG->dataroot . '/stack/maxima_opt_auto'); $lisp = '1'; // Try to guess the lisp version. if (!(false === strpos($genuinedebug, 'GNU Common Lisp (GCL)'))) { $lisp = 'GCL'; } if (!(false === strpos($genuinedebug, 'Lisp SBCL'))) { $lisp = 'SBCL'; } if (!(false === strpos($genuinedebug, 'Lisp CLISP'))) { $lisp = 'CLISP'; } switch ($lisp) { case 'GCL': $maximacommand = ':lisp (si::save-system "' . $imagename . '")' . "\n"; $maximacommand .= 'quit();' . "\n"; $commandline = stack_utils::convert_slash_paths($imagename . ' -eval \'(cl-user::run)\''); break; case 'SBCL': $maximacommand = ':lisp (sb-ext:save-lisp-and-die "' . $imagename . '" :toplevel #\'run :executable t)' . "\n"; $commandline = stack_utils::convert_slash_paths($imagename); break; case 'CLISP': $imagename .= '.mem'; $maximacommand = ':lisp (ext:saveinitmem "' . $imagename . '" :init-function #\'user::run)' . "\n"; $maximacommand .= 'quit();' . "\n"; $lisprun = shell_exec('locate lisp.run'); if (trim($lisprun) == '') { $success = false; $message = stack_string('healthautomaxopt_nolisprun'); return array($message, '', $success); } $lisprun = explode("\n", $lisprun); $commandline = $lisprun[0] . ' -q -M ' . stack_utils::convert_slash_paths($imagename); break; default: $success = false; $message = stack_string('healthautomaxopt_nolisp'); return array($message, '', $success); } // Really make sure there is no cache. list($results, $debug) = self::stackmaxima_nocache_call($maximacommand); // Question: should we at this stage try to use the optimised image we have created? $success = true; // Add the timeout command to the message. $commandline = 'timeout --kill-after=10s 10s ' . $commandline; $message = stack_string('healthautomaxopt_ok', array('command' => $commandline)); if (!file_exists($imagename)) { $success = false; $message = stack_string('healthautomaxopt_notok'); } return array($message, $debug, $success); }
/** * Get the full path to the folder where plot files are stored. * @return string the full path to where the maximalocal.mac file should be stored. */ public static function images_location() { global $CFG; return stack_utils::convert_slash_paths($CFG->dataroot . '/stack/plots'); }
/** * @return string the teacher's answer, displayed to the student in the general feedback. */ public function get_teacher_answer_display($value, $display) { $values = stack_utils::list_to_array($value, false); $values = array_map(function ($ex) { return '<code>' . $ex . '</code>'; }, $values); $value = "<br/>" . implode("<br/>", $values); return stack_string('teacheranswershow', array('value' => $value, 'display' => $display)); }
public static function clear_config_cache() { self::$config = null; }
/** * @return stack_maths_output the output method that has been set in the * configuration options. */ protected static function get_output() { if ('' == trim(stack_utils::get_config()->mathsdisplay)) { return self::get_output_instance('mathjax'); } return self::get_output_instance(stack_utils::get_config()->mathsdisplay); }
/** * This function actually evaluates the castext. */ private function instantiate() { if (!$this->valid) { return false; } // Deal with castext without any CAS variables. if (null !== $this->session) { $this->session->instantiate(); $this->errors .= $this->session->get_errors(); } $this->castext = stack_utils::wrap_around($this->trimmedcastext); if (null !== $this->session) { $this->castext = $this->session->get_display_castext($this->castext); } // Another modification. Stops <html> tags from being given $ tags. $this->castext = str_replace('\\(<html>', '', $this->castext); // Bug occurs when maxima returns <html>tags in output, eg plots or div by 0 errors. $this->castext = str_replace('</html>\\)', '', $this->castext); $this->latex_tidy(); $this->instantiated = true; }
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); }
public function test_decompose_rename_operation_complex() { $this->assertEquals(array('i' => 'j', 'h' => 'i', 'a' => 'temp1', 'e' => 'a', 'g' => 'e', 'temp1' => 'g', 'd' => 'temp2', 'f' => 'd', 'temp2' => 'f'), stack_utils::decompose_rename_operation(array('a' => 'g', 'b' => 'b', 'd' => 'f', 'd' => 'f', 'e' => 'a', 'f' => 'd', 'g' => 'e', 'h' => 'i', 'i' => 'j'))); }
protected function unpack_helper($rawresultfragment) { // Take the raw string from the CAS, and unpack this into an array. $offset = 0; $rawresultfragmentlen = strlen($rawresultfragment); $unparsed = ''; $errors = ''; if ($eqpos = strpos($rawresultfragment, '=', $offset)) { // Check there are ='s. do { $gb = stack_utils::substring_between($rawresultfragment, '[', ']', $eqpos); $val = substr($gb[0], 1, strlen($gb[0]) - 2); $val = trim($val); if (preg_match('/[-A-Za-z0-9].*/', substr($rawresultfragment, $offset, $eqpos - $offset), $regs)) { $var = trim($regs[0]); } else { $var = 'errors'; $errors['LOCVARNAME'] = "Couldn't get the name of the local variable."; } $unparsed[$var] = $val; $offset = $gb[2]; } while (($eqpos = strpos($rawresultfragment, '=', $offset)) && $offset < $rawresultfragmentlen); } else { $errors['PREPARSE'] = "There are no ='s in the raw output from the CAS!"; } if ('' != $errors) { $unparsed['errors'] = $errors; } return $unparsed; }
function report($d) { global $CFG; $dirroot = stack_utils::convert_slash_paths($CFG->dirroot . '/question/type/stack/doc/en'); $wwwroot = $CFG->wwwroot; $webdocs = $wwwroot . '/question/type/stack/doc/en'; $weburl = $wwwroot . '/question/type/stack/doc/doc.php'; $a = array(); $fileslinkedto = array(); if (is_dir($d)) { if ($dh = opendir($d)) { while (($f = readdir($dh)) !== false) { if (substr($f, 0, 1) != '.') { $fpath = "{$d}/{$f}"; if (filetype($fpath) == 'dir') { $a = array_merge($a, report($fpath)); } else { $fname = pathinfo($fpath, PATHINFO_FILENAME); $fext = pathinfo($fpath, PATHINFO_EXTENSION); $fsize = filesize($fpath); $reldir = str_replace($dirroot, '', $d); $a[] = array($fpath, 'F', 'Found file ' . "{$fpath}"); if ($fsize >= 18000) { $a[] = array($fpath, 'W', "Large file ({$fsize} bytes)"); } if ($fext != 'bak') { if ($fext != 'md') { $a[] = array($fpath, 'W', "Not a markdown file ({$fext})"); } // Let's do some link checking, step one: scrape the links off the document's web page. $links = stack_process_markdown(file_get_contents($fpath), ""); preg_match_all("/<a(?:[^>]*)href=\"([^\"]*)\"(?:[^>]*)>(?:[^<]*)<\\/a>/is", $links, $found); // The array $found[0] will have the full a tags, found[1] contains their href properties. // Step two, visit these links and check for 404s. foreach ($found[1] as $i => $link) { if (strpos($link, 'mailto:') !== 0 and strpos($link, 'maintenance.php') === false and strpos($link, 'http') !== 0) { // Don't check mailto:, this file (ARGH!) // Also if ?ext not true then better not be an external link. if (strpos($link, 'http') !== 0) { // If a local link, do some preparation. if (strpos($link, '/') === 0) { $link = $webdocs . $link; // Not a relative link. } else { $link = $webdocs . rtrim($reldir, '/') . '/' . $link; } // It looks like get_headers isn't evaluating these so lets do it manually. $segs = explode('/', $link); while (($pos = array_search('.', $segs)) !== false) { unset($segs[$pos]); } while (($pos = array_search('..', $segs)) !== false) { unset($segs[$pos], $segs[$pos - 1]); } $link = implode('/', $segs); // Finally it looks like #--- are getting parsed in the request, let's omit them. if (strpos($link, '#') !== false) { $link = substr($link, 0, strpos($link, '#')); } } $hs = get_headers($link); if (strpos($hs[0], '404') !== false) { $a[] = array($fpath, 'E', 'Error 404 [' . $found[0][$i] . '] appears to be a dead link'); } else { $fileslinkedto[$found[0][$i]] = true; } if ('/' == substr($link, -1)) { $a[] = array($fpath, 'E', 'Link [' . $found[0][$i] . '] calls a directory. This should have explicit <tt>index.md</tt> but does not.'); } } } } } } } closedir($dh); } } return $a; }
/** * Check a form field to ensure it does not contain any placeholders of given types. * @param string $fieldname the name of this field. Used in the error messages. * @param value $value the value to check. * @param array $placeholders types to check for. By default 'input', 'validation' and 'feedback'. * @return array of problems (so an empty array means all is well). */ protected function check_no_placeholders($fieldname, $value, $placeholders = array('input', 'validation', 'feedback')) { $problems = array(); foreach ($placeholders as $placeholder) { if (stack_utils::extract_placeholders($value, 'input')) { $problems[] = stack_string('fieldshouldnotcontainplaceholder', array('field' => $fieldname, 'type' => $placeholder)); } } return $problems; }
public function validation($data, $files) { $errors = parent::validation($data, $files); $question = $this->_customdata; // Inputs. $inputnames = array(); foreach ($question->inputs as $inputname => $notused) { $field = 'inputname_' . $inputname; $proposedname = $data[$field]; if (!stack_utils::is_valid_name($proposedname)) { $errors[$field] = stack_string('notavalidname'); } else { if (array_key_exists($proposedname, $inputnames)) { $errors[$field] = stack_string('namealreadyused'); } else { $inputnames[$proposedname] = $inputname; } } } // PRTs. $prtnames = array(); foreach ($question->prts as $prtname => $notused) { $field = 'prtname_' . $prtname; $proposedname = $data[$field]; if (!stack_utils::is_valid_name($proposedname)) { $errors[$field] = stack_string('notavalidname'); } else { if (array_key_exists($proposedname, $prtnames)) { $errors[$field] = stack_string('namealreadyused'); } else { $prtnames[$proposedname] = $prtname; } } } foreach ($question->prts as $prtname => $prt) { $nodenames = array(); $nodes = $prt->get_nodes_summary(); foreach ($nodes as $nodekey => $notused) { $field = 'nodename_' . $prtname . '_' . $nodekey; $proposedname = $data[$field]; if ($proposedname < 1) { $errors[$field] = stack_string('notavalidname'); } else { if (array_key_exists($proposedname, $nodenames)) { $errors[$field] = stack_string('namealreadyused'); } else { $nodenames[$proposedname] = $nodekey; } } } } return $errors; }
private function strings_remove($cmd) { $strings = stack_utils::all_substring_strings($cmd); foreach ($strings as $key => $string) { $cmd = str_replace('"' . $string . '"', '[STR:' . $key . ']', $cmd); } return array($cmd, $strings); }
// Rename the PRT nodes. foreach ($question->prts as $prtname => $prt) { $noderenames = array(); foreach ($prt->get_nodes_summary() as $nodekey => $notused) { $noderenames[$nodekey] = $data->{'nodename_' . $prtname . '_' . $nodekey} - 1; } foreach (stack_utils::decompose_rename_operation($noderenames) as $from => $to) { $qtype->rename_prt_node($question->id, $prtname, $from, $to); } } // Rename the PRTs. Much easier to do this after the nodes. $prtrenames = array(); foreach ($question->prts as $prtname => $notused) { $prtrenames[$prtname] = $data->{'prtname_' . $prtname}; } foreach (stack_utils::decompose_rename_operation($prtrenames) as $from => $to) { $qtype->rename_prt($question->id, $from, $to); } // Done. $transaction->allow_commit(); redirect($returnurl); } } // Start output. echo $OUTPUT->header(); echo $OUTPUT->heading($title); // Display the question. echo $OUTPUT->heading(stack_string('questionpreview'), 3); echo $quba->render_question($slot, $options); // Display the form to rename bits of the question. $form->display();