PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/question/type/algebra/edit_algebra_form.php

https://bitbucket.org/systime/screening2
PHP | 395 lines | 241 code | 30 blank | 124 comment | 46 complexity | fde61f8a4a47d65ed172811944fb1532 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, GPL-3.0, BSD-3-Clause, LGPL-2.0
  1. <?php
  2. /**
  3. * Defines the editing form for the algebra question type.
  4. *
  5. * @copyright &copy; 2008 Roger Moore
  6. * @author Roger Moore <rwmoore@ualberta.ca>
  7. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  8. * @package questionbank
  9. * @subpackage questiontypes
  10. */
  11. require_once("parser.php");
  12. // Override the default number of answers and the number to add to avoid clutter.
  13. // Algebra questions will likely not have huge number of different answers...
  14. define("SYMB_QUESTION_NUMANS_START", 2);
  15. define("SYMB_QUESTION_NUMANS_ADD", 1);
  16. // Override the default number of answers and the number to add to avoid clutter.
  17. // algebra questions will likely not have huge number of different answers...
  18. define("SYMB_QUESTION_NUMVAR_START", 2);
  19. define("SYMB_QUESTION_NUMVAR_ADD", 1);
  20. /**
  21. * symoblic editing form definition.
  22. */
  23. class question_edit_algebra_form extends question_edit_form {
  24. /**
  25. * Add question-type specific form fields.
  26. *
  27. * @param MoodleQuickForm $mform the form being built.
  28. */
  29. function definition_inner(&$mform) {
  30. // Add the select control which will select the comparison type to use
  31. $mform->addElement('select', 'compareby', get_string('compareby','qtype_algebra'),
  32. array( "sage" => get_string('comparesage', 'qtype_algebra'),
  33. "eval" => get_string('compareeval', 'qtype_algebra'),
  34. "equiv" => get_string('compareequiv','qtype_algebra')
  35. ));
  36. $mform->setDefault('compareby','eval');
  37. $mform->setHelpButton('compareby',array('comp_algorithm',get_string('compalgorithm','qtype_algebra'),
  38. 'qtype_algebra'));
  39. // Add the control to select the number of checks to perform
  40. // First create an array with all the allowed values. We will then use this array
  41. // with the array_combine function to create a single array where the keys are the
  42. // same as the array values
  43. $chk_array=array( '1', '2', '3', '5', '7',
  44. '10', '20', '30', '50', '70',
  45. '100', '200', '300', '500', '700', '1000');
  46. // Add the select element using the array_combine method discussed above
  47. $mform->addElement('select', 'nchecks', get_string('nchecks','qtype_algebra'),
  48. array_combine($chk_array,$chk_array));
  49. // Set the default number of checks to perform
  50. $mform->setDefault('nchecks','10');
  51. $mform->setHelpButton('nchecks',array('eval_checks',get_string('evalchecks','qtype_algebra'),
  52. 'qtype_algebra'));
  53. // Add the box to set the tolerance to use when performing evaluation checks
  54. $mform->addElement('text', 'tolerance', get_string('tolerance','qtype_algebra'));
  55. $mform->setType('tolerance', PARAM_NUMBER);
  56. $mform->setDefault('tolerance','0.001');
  57. $mform->setHelpButton('tolerance',array('check_tolerance',get_string('checktolerance','qtype_algebra'),
  58. 'qtype_algebra'));
  59. // Add an entry for the answer box prefix
  60. $mform->addElement('text', 'answerprefix', get_string('answerprefix','qtype_algebra'),array('size'=>55));
  61. $mform->setType('answerprefix', PARAM_RAW);
  62. $mform->setHelpButton('answerprefix',array('answer_prefix',get_string('answerboxprefix','qtype_algebra'),
  63. 'qtype_algebra'));
  64. // Add an entry for a disallowed expression
  65. $mform->addElement('text', 'disallow', get_string('disallow','qtype_algebra'),array('size'=>55));
  66. $mform->setType('disallow', PARAM_RAW);
  67. $mform->setHelpButton('disallow',array('disallowed_ans',get_string('disallowanswer','qtype_algebra'),
  68. 'qtype_algebra'));
  69. // Create an array which will store the function checkboxes
  70. $func_group=array();
  71. // Create an array to add spacers between the boxes
  72. $spacers=array('<br>');
  73. // Add the initial all functions box to the list of check boxes
  74. $func_group[] =& $mform->createElement('checkbox','all','',get_string('allfunctions','qtype_algebra'));
  75. // Create a checkbox element for each function understood by the parser
  76. for($i=0;$i<count(qtype_algebra_parser::$functions);$i++) {
  77. $func=qtype_algebra_parser::$functions[$i];
  78. $func_group[] =& $mform->createElement('checkbox',$func,'',$func);
  79. if(($i % 6) == 5) {
  80. $spacers[]='<br>';
  81. } else {
  82. $spacers[]=str_repeat('&nbsp;',8-strlen($func));
  83. }
  84. }
  85. // Create and add the group of function controls to the form
  86. $mform->addGroup($func_group,'allowedfuncs',get_string('allowedfuncs','qtype_algebra'),$spacers,true);
  87. $mform->disabledIf('allowedfuncs','allowedfuncs[all]','checked');
  88. $mform->setDefault('allowedfuncs[all]','checked');
  89. $mform->setHelpButton('allowedfuncs',array('allowed_funcs',get_string('allowedfunctions','qtype_algebra'),
  90. 'qtype_algebra'));
  91. // Create the array for the list of variables used in the question
  92. $repeated=array();
  93. // Create the array for the list of repeated options used by the variable subforms
  94. $repeatedoptions = array();
  95. // Add the form elements to enter the variables
  96. $repeated[] =& $mform->createElement('header','variablehdr',get_string('variableno','qtype_algebra','{no}'));
  97. $repeatedoptions['variablehdr']['helpbutton'] = array('variable',get_string('variable','qtype_algebra'),
  98. 'qtype_algebra');
  99. $repeated[] =& $mform->createElement('text','variable',get_string('variablename','qtype_algebra'),array('size'=>20));
  100. $mform->setType('variable', PARAM_RAW);
  101. $repeated[] =& $mform->createElement('text','varmin',get_string('varmin','qtype_algebra'),array('size'=>20));
  102. $mform->setType('varmin', PARAM_RAW);
  103. $repeatedoptions['varmin']['default'] = '';
  104. $repeated[] =& $mform->createElement('text','varmax',get_string('varmax','qtype_algebra'),array('size'=>20));
  105. $mform->setType('varmax', PARAM_RAW);
  106. $repeatedoptions['varmax']['default'] = '';
  107. // Get the current number of variables defined, if any
  108. if (isset($this->question->options)) {
  109. $countvars = count($this->question->options->variables);
  110. } else {
  111. $countvars = 0;
  112. }
  113. // Come up with the number of variable entries to add to the form at the start
  114. if ($this->question->formoptions->repeatelements){
  115. $repeatsatstart = (SYMB_QUESTION_NUMVAR_START > ($countvars + SYMB_QUESTION_NUMVAR_ADD))?
  116. SYMB_QUESTION_NUMVAR_START : ($countvars + SYMB_QUESTION_NUMVAR_ADD);
  117. } else {
  118. $repeatsatstart = $countvars;
  119. }
  120. $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'novariables', 'addvariables',
  121. SYMB_QUESTION_NUMVAR_ADD, get_string('addmorevariableblanks', 'qtype_algebra'));
  122. // Add the instructions for entering answers to the question
  123. $mform->addElement('static', 'answersinstruct', get_string('correctanswers', 'quiz'), get_string('filloutoneanswer', 'quiz'));
  124. $mform->closeHeaderBefore('answersinstruct');
  125. $creategrades = get_grade_options();
  126. $gradeoptions = $creategrades->gradeoptions;
  127. $repeated = array();
  128. $repeated[] =& $mform->createElement('header', 'answerhdr', get_string('answerno', 'qtype_algebra', '{no}'));
  129. $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 54));
  130. $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions);
  131. $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'),
  132. array('course' => $this->coursefilesid));
  133. if (isset($this->question->options)){
  134. $countanswers = count($this->question->options->answers);
  135. } else {
  136. $countanswers = 0;
  137. }
  138. if ($this->question->formoptions->repeatelements){
  139. $repeatsatstart = (SYMB_QUESTION_NUMANS_START > ($countanswers + SYMB_QUESTION_NUMANS_ADD))?
  140. SYMB_QUESTION_NUMANS_START : ($countanswers + SYMB_QUESTION_NUMANS_ADD);
  141. } else {
  142. $repeatsatstart = $countanswers;
  143. }
  144. $repeatedoptions = array();
  145. $mform->setType('answer', PARAM_RAW);
  146. $repeatedoptions['fraction']['default'] = 0;
  147. $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers',
  148. SYMB_QUESTION_NUMANS_ADD, get_string('addmoreanswerblanks', 'qtype_algebra'));
  149. }
  150. /**
  151. * Sets the existing values into the form for the question specific data.
  152. *
  153. * This method copies the data from the existing database record into the form fields as default
  154. * values for the various elements.
  155. *
  156. * @param $question the question object from the database being used to fill the form
  157. */
  158. function set_data($question) {
  159. // Check to see if there are any existing question options, if not then just call
  160. // the base class set data method and exit
  161. if (!isset($question->options)) {
  162. return parent::set_data($question);
  163. }
  164. // Our first job is to set the defaults for the answers. Start by getting the answers...
  165. $answers = $question->options->answers;
  166. // If we found any answers then loop over them using a numerical key to provide an index
  167. // to the arrays we need to access in the form
  168. if (count($answers)) {
  169. $key = 0;
  170. foreach ($answers as $answer) {
  171. // For every answer set the default values
  172. $default_values['answer['.$key.']'] = $answer->answer;
  173. $default_values['fraction['.$key.']'] = $answer->fraction;
  174. $default_values['feedback['.$key.']'] = $answer->feedback;
  175. $key++;
  176. }
  177. }
  178. // Now we do exactly the same for the variables...
  179. $vars = $question->options->variables;
  180. // If we found any answers then loop over them using a numerical key to provide an index
  181. // to the arrays we need to access in the form
  182. if (count($vars)) {
  183. $key = 0;
  184. foreach ($vars as $var) {
  185. // For every variable set the default values
  186. $default_values['variable['.$key.']'] = $var->name;
  187. // Only set the mon and max defaults if this variable has a range
  188. if($var->min!='') {
  189. $default_values['varmin['.$key.']'] = $var->min;
  190. $default_values['varmax['.$key.']'] = $var->max;
  191. }
  192. $key++;
  193. }
  194. }
  195. // Add the default values for the allowed functions controls
  196. // First check to see if there are any allowed functions defined
  197. if(count($question->options->allowedfuncs)>0) {
  198. // Clear the 'all functions' flag since functions are restricted
  199. $default_values['allowedfuncs[all]']=0;
  200. // Loop over all the functions which the parser understands
  201. foreach(qtype_algebra_parser::$functions as $func) {
  202. // For each function see if the function is in the allowed function
  203. // list and if so set the check box otherwise remove the check box
  204. if(in_array($func,$question->options->allowedfuncs)) {
  205. $default_values['allowedfuncs['.$func.']']=1;
  206. } else {
  207. $default_values['allowedfuncs['.$func.']']=0;
  208. }
  209. }
  210. }
  211. // There are no allowed functions defined so all functions are allowed
  212. else {
  213. $default_values['allowedfuncs[all]']=1;
  214. }
  215. // Add the default values to the question object in a form which the parent
  216. // set data method will be able to use to find the default values
  217. $question = (object)((array)$question + $default_values);
  218. // Finally call the parent set data method to handle everything else
  219. parent::set_data($question);
  220. }
  221. /**
  222. * Validates the form data ensuring there are no obvious errors in the submitted data.
  223. *
  224. * This method performs some basic sanity checks on the form data before it gets converted
  225. * into a database record.
  226. *
  227. * @param $data the data from the form which needs to be checked
  228. * @param $files some files - I don't know what this is for! - files defined in the form??
  229. */
  230. function validation($data, $files) {
  231. // Call the base class validation method and keep any errors it generates
  232. $errors = parent::validation($data, $files);
  233. // Regular expression string to match a number
  234. $renumber='/([+-]*(([0-9]+\.[0-9]*)|([0-9]+)|(\.[0-9]+))|'.
  235. '(([0-9]+\.[0-9]*)|([0-9]+)|(\.[0-9]+))E([-+]?\d+))/A';
  236. // Perform sanity checks on the variables.
  237. $vars = $data['variable'];
  238. // Create an array of defined variables
  239. $varlist=array();
  240. foreach ($vars as $key => $var) {
  241. $trimvar = trim($var);
  242. $trimmin = trim($data['varmin'][$key]);
  243. $trimmax = trim($data['varmax'][$key]);
  244. // Check that there is a valid variable name otherwise skip
  245. if ($trimvar == '') {
  246. continue;
  247. }
  248. // Check that this variable does not have the same name as a function
  249. if(in_array($trimvar,qtype_algebra_parser::$functions) or in_array($trimvar,qtype_algebra_parser::$specials)) {
  250. $errors['variable['.$key.']'] = get_string('illegalvarname','qtype_algebra',$trimvar);
  251. }
  252. // Check that this variable has not been defined before
  253. if(in_array($trimvar,$varlist)) {
  254. $errors['variable['.$key.']'] = get_string('duplicatevar','qtype_algebra');
  255. } else {
  256. // Add the variable to the list of defined variables
  257. $varlist[]=$trimvar;
  258. }
  259. // If the comparison algorithm selected is evaluate then ensure that each variable
  260. // has a valid minimum and maximum defined. For the other types of comparison we can
  261. // ignore the range
  262. if($data['compareby']=='eval') {
  263. // Check that a minimum has been defined
  264. if ($trimmin == '') {
  265. $errors['varmin['.$key.']'] = get_string('novarmin','qtype_algebra');
  266. }
  267. // If there is one check that it is a number
  268. else if(!preg_match($renumber,$trimmin)) {
  269. $errors['varmin['.$key.']'] = get_string('notanumber','qtype_algebra');
  270. }
  271. if ($trimmax == '') {
  272. $errors['varmax['.$key.']'] = get_string('novarmax','qtype_algebra');
  273. }
  274. // If there is one check that it is a number
  275. else if(!preg_match($renumber,$trimmax)) {
  276. $errors['varmax['.$key.']'] = get_string('notanumber','qtype_algebra');
  277. }
  278. // Check that the minimum is less that the maximum!
  279. if ((float)$trimmin > (float)$trimmax) {
  280. $errors['varmin['.$key.']'] = get_string('varmingtmax','qtype_algebra');
  281. }
  282. } // end check for eval type
  283. } // end loop over variables
  284. // Check that at least one variable is defined
  285. if (count($varlist)==0) {
  286. $errors['variable[0]'] = get_string('notenoughvars', 'qtype_algebra');
  287. }
  288. // Now perform the sanity checks on the answers
  289. // Create a parser which we will use to check that the answers are understandable
  290. $p = new qtype_algebra_parser;
  291. $answers = $data['answer'];
  292. $answercount = 0;
  293. $maxgrade = false;
  294. // Create an empty array to store the used variables
  295. $ansvars=array();
  296. // Create an empty array to store the used functions
  297. $ansfuncs=array();
  298. // Loop over all the answers in the form
  299. foreach ($answers as $key => $answer) {
  300. // Try to parse the answer string using the parser. If this fails it will
  301. // throw an exception which we catch to generate the associated error string
  302. // for the expression
  303. try {
  304. $expr=$p->parse($answer);
  305. // Add any new variables to the list we are keeping. First we get the list
  306. // of variables in this answer. Then we get the array of variables which are
  307. // in this answer that are not in any previous answer (using array_diff).
  308. // Finally we merge this difference array with the list of all variables so far
  309. $tmpvars=$expr->get_variables();
  310. $ansvars=array_merge($ansvars,array_diff($tmpvars,$ansvars));
  311. // Check that all the variables in this answer have been declared
  312. // Do this by looking for a non-empty array to be returned from the array_diff
  313. // between the list of all declared variables and the variables in this answer
  314. if($d=array_diff($tmpvars,$varlist)) {
  315. $errors['answer['.$key.']'] = get_string('undefinedvar','qtype_algebra',"'".implode("', '",$d)."'");
  316. }
  317. // Do the same for functions which we did for variables
  318. $ansfuncs=array_merge($ansfuncs,array_diff($expr->get_functions(),$ansfuncs));
  319. // Check that this is not an empty answer
  320. if (!is_a($expr,"qtype_algebra_parser_nullterm")) {
  321. // Increase the number of answers
  322. $answercount++;
  323. // Check to see if the answer has the maximum grade
  324. if ($data['fraction'][$key] == 1) {
  325. $maxgrade = true;
  326. }
  327. }
  328. } catch (Exception $e) {
  329. $errors['answer['.$key.']']=$e->getMessage();
  330. // Return here because subsequent errors may be wrong due to not counting the answer
  331. // which just failed to parse
  332. return $errors;
  333. }
  334. }
  335. // Check that we have at least one answer!
  336. if ($answercount==0){
  337. $errors['answer[0]'] = get_string('notenoughanswers', 'quiz', 1);
  338. }
  339. // Check that at least one question has the maximum possible grade
  340. if ($maxgrade == false) {
  341. $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
  342. }
  343. // Check for variables which are defined but never used.
  344. // Do this by looking for a non-empty array to be returned from array_diff.
  345. if($d=array_diff($varlist,$ansvars)) {
  346. // Loop over all the variables in the form
  347. foreach ($vars as $key => $var) {
  348. $trimvar = trim($var);
  349. // If the variable is in the unused array then add the error message to that variable
  350. if(in_array($trimvar,$d)) {
  351. $errors['variable['.$key.']'] = get_string('unusedvar','qtype_algebra');
  352. }
  353. }
  354. }
  355. // Check that the tolerance is greater than or equal to zero
  356. if($data['tolerance']<0) {
  357. $errors['tolerance']=get_string('toleranceltzero','qtype_algebra');
  358. }
  359. return $errors;
  360. }
  361. function qtype() {
  362. return 'algebra';
  363. }
  364. }
  365. ?>