コード例 #1
0
 public function setUp()
 {
     parent::setUp();
     stack_utils::clear_config_cache();
     self::setup_test_maxima_connection($this);
     $this->resetAfterTest();
 }
コード例 #2
0
 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 &lt;tex mode="inline"&gt;x^2&lt;/tex&gt;?', stack_maths::process_display_castext('What is \\(x^2\\)?'));
     // Test CAS text with display maths.
     $this->assertEquals('What is <span class="displayequation">&lt;tex mode="display"&gt;x^2&lt;/tex&gt;</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 &lt;tex mode="inline"&gt;x^2&lt;/tex&gt; or ' . '<span class="displayequation">&lt;tex mode="display"&gt;x^2&lt;/tex&gt;</span>?', stack_maths::process_display_castext('What is $x^2$ or $$x^2$$?'));
     stack_utils::clear_config_cache();
 }
コード例 #3
0
/**
 * 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>';
}
コード例 #4
0
 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();
 }
コード例 #5
0
 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;
 }
コード例 #6
0
 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;
 }
コード例 #7
0
 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;
 }
コード例 #8
0
 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();
 }
コード例 #9
0
 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();
 }
コード例 #10
0
 /**
  * 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);
 }
コード例 #11
0
 /**
  * @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;
 }
コード例 #12
0
// 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') {
コード例 #13
0
 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);
 }
コード例 #14
0
 /**
  * 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');
 }
コード例 #15
0
 /**
  * @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));
 }
コード例 #16
0
 public static function clear_config_cache()
 {
     self::$config = null;
 }
コード例 #17
0
 /**
  * @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);
 }
コード例 #18
0
 /**
  * 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;
 }
コード例 #19
0
 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);
 }
コード例 #20
0
 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')));
 }
コード例 #21
0
 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;
 }
コード例 #22
0
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;
}
コード例 #23
0
 /**
  * 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;
 }
コード例 #24
0
 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;
 }
コード例 #25
0
 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);
 }
コード例 #26
0
        // 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();