public function test_check_nested_bookends() { $this->assertTrue(stack_utils::check_nested_bookends('')); $this->assertTrue(stack_utils::check_nested_bookends('x+1')); $this->assertTrue(stack_utils::check_nested_bookends('(sin(x)+1)')); $this->assertTrue(stack_utils::check_nested_bookends('[sin(x)+1]')); $this->assertTrue(stack_utils::check_nested_bookends('{}[]()')); $this->assertTrue(stack_utils::check_nested_bookends('{[()]}')); $this->assertTrue(stack_utils::check_nested_bookends('{[()(()[(){}((){})])]}')); $this->assertFalse(stack_utils::check_nested_bookends('(')); $this->assertFalse(stack_utils::check_nested_bookends(')')); $this->assertFalse(stack_utils::check_nested_bookends('x+1)')); $this->assertFalse(stack_utils::check_nested_bookends('(sin(x+1)')); $this->assertFalse(stack_utils::check_nested_bookends('[sin(x]+1)')); $this->assertFalse(stack_utils::check_nested_bookends('{}[()')); $this->assertFalse(stack_utils::check_nested_bookends('{[()(()[(){}((){})]))]}')); }
private function validate($security = 's', $syntax = true, $insertstars = 0, $allowwords = '') { if (!('s' === $security || 't' === $security)) { throw new stack_exception('stack_cas_casstring: security level, must be "s" or "t" only.'); } if (!is_bool($syntax)) { throw new stack_exception('stack_cas_casstring: syntax, must be Boolean.'); } if (!is_int($insertstars)) { throw new stack_exception('stack_cas_casstring: insertstars, must be an integer.'); } $this->valid = true; $this->casstring = $this->rawcasstring; $cmd = $this->rawcasstring; // CAS strings must be non-empty. if (trim($this->casstring) == '') { $this->answernote[] = 'empty'; $this->valid = false; return false; } // CAS strings may not contain @ or $. if (strpos($cmd, '@') !== false || strpos($cmd, '$') !== false) { $this->add_error(stack_string('illegalcaschars')); $this->answernote[] = 'illegalcaschars'; $this->valid = false; return false; } // Check for matching string delimiters. $cmdsafe = str_replace('\\"', '', $cmd); if (stack_utils::check_matching_pairs($cmdsafe, '"') == false) { $this->errors .= stack_string('stackCas_MissingString'); $this->answernote[] = 'MissingString'; $this->valid = false; } // Now remove any strings from the $cmd. list($cmd, $strings) = $this->strings_remove($cmd); // Search for HTML fragments. This is hard to do because < is an infix operator! // We cannot search for arbitrary closing tags, e.g. for the pattern '</' because // we pass back strings with HTML in when we have already evaluated plots! $htmlfragments = array('<span', '</span>', '<p>', '</p>'); foreach ($htmlfragments as $frag) { if (strpos($cmd, $frag) !== false) { $this->add_error(stack_string('htmlfragment') . ' <pre>' . $this->strings_replace($cmd, $strings) . '</pre>'); $this->answernote[] = 'htmlfragment'; $this->valid = false; return false; } } // If student, check for spaces between letters or numbers in expressions. if ($security != 't') { $pat = "|([A-Za-z0-9\\(\\)]+) ([A-Za-z0-9\\(\\)]+)|"; // Special case - allow students to type in expressions such as "x>1 and x<4". $cmdmod = str_replace(' or ', '', $cmd); $cmdmod = str_replace(' and ', '', $cmdmod); $cmdmod = str_replace('not ', '', $cmdmod); if (preg_match($pat, $cmdmod)) { $cmds = str_replace(' ', '<font color="red">_</font>', $this->strings_replace($cmd, $strings)); $this->add_error(stack_string('stackCas_spaces', array('expr' => stack_maxima_format_casstring($cmds)))); $this->answernote[] = 'spaces'; $this->valid = false; } } // Check for % signs, allow %pi %e, %i, %gamma, %phi but nothing else. if (strstr($cmd, '%') !== false) { $cmdl = strtolower($cmd); preg_match_all("(\\%.*)", $cmdl, $found); foreach ($found[0] as $match) { if (!(strpos($match, '%e') !== false || strpos($match, '%pi') !== false || strpos($match, '%i') !== false || strpos($match, '%j') !== false || strpos($match, '%gamma') !== false || strpos($match, '%phi') !== false)) { // Constants %e and %pi are allowed. Any other percentages dissallowed. $this->add_error(stack_string('stackCas_percent', array('expr' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); $this->answernote[] = 'percent'; $this->valid = false; } } } $inline = stack_utils::check_bookends($cmd, '(', ')'); if ($inline !== true) { // The method check_bookends does not return false. $this->valid = false; if ($inline == 'left') { $this->answernote[] = 'missingLeftBracket'; $this->add_error(stack_string('stackCas_missingLeftBracket', array('bracket' => '(', 'cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } else { $this->answernote[] = 'missingRightBracket'; $this->add_error(stack_string('stackCas_missingRightBracket', array('bracket' => ')', 'cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } } $inline = stack_utils::check_bookends($cmd, '{', '}'); if ($inline !== true) { // The method check_bookends does not return false. $this->valid = false; if ($inline == 'left') { $this->answernote[] = 'missingLeftBracket'; $this->add_error(stack_string('stackCas_missingLeftBracket', array('bracket' => '{', 'cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } else { $this->answernote[] = 'missingRightBracket'; $this->add_error(stack_string('stackCas_missingRightBracket', array('bracket' => '}', 'cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } } $inline = stack_utils::check_bookends($cmd, '[', ']'); if ($inline !== true) { // The method check_bookends does not return false. $this->valid = false; if ($inline == 'left') { $this->answernote[] = 'missingLeftBracket'; $this->add_error(stack_string('stackCas_missingLeftBracket', array('bracket' => '[', 'cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } else { $this->answernote[] = 'missingRightBracket'; $this->add_error(stack_string('stackCas_missingRightBracket', array('bracket' => ']', 'cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } } if (!stack_utils::check_nested_bookends($cmd)) { $this->valid = false; $this->add_error(stack_string('stackCas_bracketsdontmatch', array('cmd' => stack_maxima_format_casstring($this->strings_replace($cmd, $strings))))); } if ($security == 's') { // Check for apostrophes if a student. if (strpos($cmd, "'") !== false) { $this->add_error(stack_string('stackCas_apostrophe')); $this->answernote[] = 'apostrophe'; $this->valid = false; } // Check new lines. if (strpos($cmd, "\n") !== false) { $this->add_error(stack_string('stackCas_newline')); $this->answernote[] = 'newline'; $this->valid = false; } } if ($security == 's') { // Check for bad looking trig functions, e.g. sin^2(x) or tan*2*x // asin etc, will be included automatically, so we don't need them explicitly. $triglist = array('sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'sec', 'cosec', 'cot', 'csc', 'coth', 'csch', 'sech'); $funlist = array('log', 'ln', 'lg', 'exp', 'abs', 'sqrt'); foreach (array_merge($triglist, $funlist) as $fun) { if (strpos($cmd, $fun . '^') !== false) { $this->add_error(stack_string('stackCas_trigexp', array('forbid' => stack_maxima_format_casstring($fun . '^')))); $this->answernote[] = 'trigexp'; $this->valid = false; break; } if (strpos($cmd, $fun . '[') !== false) { $this->add_error(stack_string('stackCas_trigparens', array('forbid' => stack_maxima_format_casstring($fun . '(x)')))); $this->answernote[] = 'trigparens'; $this->valid = false; break; } $opslist = array('*', '+', '-', '/'); foreach ($opslist as $op) { if (strpos($cmd, $fun . $op) !== false) { $this->add_error(stack_string('stackCas_trigop', array('trig' => stack_maxima_format_casstring($fun), 'forbid' => stack_maxima_format_casstring($fun . $op)))); $this->answernote[] = 'trigop'; $this->valid = false; break; } } } foreach ($triglist as $fun) { if (strpos($cmd, 'arc' . $fun) !== false) { $this->add_error(stack_string('stackCas_triginv', array('badinv' => stack_maxima_format_casstring('arc' . $fun), 'goodinv' => stack_maxima_format_casstring('a' . $fun)))); $this->answernote[] = 'triginv'; $this->valid = false; break; } } } // Only permit the following characters to be sent to the CAS. $cmd = trim($cmd); $allowedcharsregex = '~[^' . preg_quote(self::$allowedchars, '~') . ']~u'; // Check for permitted characters. if (preg_match_all($allowedcharsregex, $cmd, $matches)) { $invalidchars = array(); foreach ($matches as $match) { $badchar = $match[0]; if (!array_key_exists($badchar, $invalidchars)) { $invalidchars[$badchar] = $badchar; } } $this->add_error(stack_string('stackCas_forbiddenChar', array('char' => implode(", ", array_unique($invalidchars))))); $this->answernote[] = 'forbiddenChar'; $this->valid = false; } // Check for disallowed final characters, / * + - ^ £ # = & ~ |, ? : ;. $disallowedfinalcharsregex = '~[' . preg_quote(self::$disallowedfinalchars, '~') . ']$~u'; if (preg_match($disallowedfinalcharsregex, $cmd, $match)) { $this->valid = false; $a = array(); $a['char'] = $match[0]; $a['cmd'] = stack_maxima_format_casstring($this->strings_replace($cmd, $strings)); $this->add_error(stack_string('stackCas_finalChar', $a)); $this->answernote[] = 'finalChar'; } // Check for empty parentheses `()`. if (strpos($cmd, '()') !== false) { $this->valid = false; $this->add_error(stack_string('stackCas_forbiddenWord', array('forbid' => stack_maxima_format_casstring('()')))); $this->answernote[] = 'forbiddenWord'; } // Check for spurious operators. $spuriousops = array('<>', '||', '&', '..', ',,', '/*', '*/'); foreach ($spuriousops as $op) { if (substr_count($cmd, $op) > 0) { $this->valid = false; $a = array(); $a['cmd'] = stack_maxima_format_casstring($op); $this->add_error(stack_string('stackCas_spuriousop', $a)); $this->answernote[] = 'spuriousop'; } } // CAS strings may not contain // * reversed inequalities, i.e =< is not permitted in place of <=. // * chained inequalities 1<x<=3. if (strpos($cmd, '=<') !== false || strpos($cmd, '=>') !== false) { if (strpos($cmd, '=<') !== false) { $a['cmd'] = stack_maxima_format_casstring('=<'); } else { $a['cmd'] = stack_maxima_format_casstring('=>'); } $this->add_error(stack_string('stackCas_backward_inequalities', $a)); $this->answernote[] = 'backward_inequalities'; $this->valid = false; } else { if (!$this->check_chained_inequalities($cmd)) { $this->add_error(stack_string('stackCas_chained_inequalities')); $this->answernote[] = 'chained_inequalities'; $this->valid = false; } } // Commas not inside brackets either should be, or indicate a decimal number not // using the decimal point. In either case this is problematic. // For now, we just look for expressions with a comma, but without brackets. // [TODO]: improve this test to really look for unencapsulated commas. if (!(false === strpos($cmd, ',')) && !(!(false === strpos($cmd, '(')) || !(false === strpos($cmd, '[')) || !(false === strpos($cmd, '{')))) { $this->add_error(stack_string('stackCas_unencpsulated_comma')); $this->answernote[] = 'unencpsulated_comma'; $this->valid = false; } $this->check_stars($security, $syntax, $insertstars); $this->check_security($security, $allowwords); $this->key_val_split(); return $this->valid; }