/modules/question/classes/question.php

https://github.com/kodeplay/kodelearn · PHP · 251 lines · 121 code · 26 blank · 104 comment · 16 complexity · 9a8cf31de3b48509c8dfb6c5b4b6e6a2 MD5 · raw file

  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. abstract class Question {
  3. /**
  4. * @type int
  5. * A pseudo index or question number that will be given to this question
  6. * while a test is being taken
  7. * Its NOT the question_id
  8. */
  9. protected $_idx;
  10. /**
  11. * @type Model_Question
  12. */
  13. protected $_orm;
  14. /**
  15. * Type of the question
  16. */
  17. protected $_type;
  18. /**
  19. * Question attributes
  20. */
  21. protected $_attributes = array();
  22. /**
  23. * Array of error if the submitted form is invalid
  24. */
  25. protected $_validation_errors = array();
  26. /**
  27. * What the solution tabs will be called
  28. */
  29. protected $_solution_tab;
  30. /**
  31. * How many marks is will a correct answer to this question give
  32. * Default value is 1
  33. * Can be set from outside using a setter function
  34. */
  35. protected $_marks = 1;
  36. /**
  37. * Method to return a new instance of Question or its subclasses
  38. * @param mixed (int, string, Model_Question) $input
  39. */
  40. public static function factory($input) {
  41. if (is_string($input)) {
  42. $type = trim($input);
  43. $class = 'Question_' . ucfirst($type);
  44. return new $class;
  45. } elseif (is_int($input)) {
  46. $question = ORM::factory('question', $input);
  47. $class = 'Question_' . ucfirst($question->type);
  48. return new $class($question);
  49. } elseif ($input instanceof Model_Question) {
  50. $class = 'Question_' . ucfirst($input->type);
  51. return new $class($input);
  52. } else {
  53. throw new Exception('unknown type of parameter passed');
  54. }
  55. }
  56. public function __construct($question=null) {
  57. if ($question instanceof Model_Question) {
  58. $this->_orm = $question;
  59. } elseif (is_int($question)) {
  60. $this->_orm = ORM::factory('question', $question);
  61. }
  62. }
  63. /**
  64. * Method to get the form html for the solution section of a question
  65. * @param Array $postdata
  66. * @return String html
  67. */
  68. abstract public function render_form($postdata=array());
  69. /**
  70. * Method to process the attributes coming from the submitted form and
  71. * process them to the array format as stored in the db
  72. */
  73. abstract public function process_attrs($data);
  74. /**
  75. * Method to get the type of the solution tab name
  76. * @return String type
  77. */
  78. abstract public function solution_tab();
  79. /**
  80. * Method to be implemented by subclasses to validate attributes as per
  81. * the type of the question.
  82. * @param Array $attributes - These attributes are what is submitted in the form
  83. * ie non-processed attributes
  84. */
  85. abstract public function validate_attributes(array $attributes);
  86. /**
  87. * Public getter for $_orm
  88. */
  89. public function orm() {
  90. return $this->_orm;
  91. }
  92. /**
  93. * Method to get the type of the questions
  94. * @return String type
  95. */
  96. public function type() {
  97. return $this->_type;
  98. }
  99. /**
  100. * Combined Setter/Getter Method for the idx of the questions
  101. * (refer property declaration for more info on what is idx)
  102. * It is _not_ equal to the question_id in database
  103. * @return String idx
  104. */
  105. public function idx($idx=null) {
  106. if ($idx == null) {
  107. return $this->_idx;
  108. }
  109. $this->_idx = $idx;
  110. }
  111. /**
  112. * Combined setter-getter for marks
  113. */
  114. public function marks($marks=null) {
  115. if ($marks == null) {
  116. return $this->_marks;
  117. }
  118. if (is_numeric($marks)) {
  119. $this->_marks = $marks;
  120. }
  121. }
  122. /**
  123. * Getter for $_validation_errors Array
  124. * @return Array
  125. */
  126. public function validation_errors($key=null) {
  127. if ($key === null) {
  128. return $this->_validation_errors;
  129. } else {
  130. return Arr::get($this->_validation_errors, $key, '');
  131. }
  132. }
  133. public function process_hints($hintdata) {
  134. $hints = array();
  135. $total = count($hintdata['hint']);
  136. for ($i = 0; $i < $total; $i++) {
  137. if (!$hintdata['hint'][$i]) {
  138. continue;
  139. }
  140. $hints[] = array(
  141. 'hint' => $hintdata['hint'][$i],
  142. 'sort_order' => (int) $hintdata['sort_order'][$i],
  143. 'deduction' => (float) $hintdata['deduction'][$i]
  144. );
  145. }
  146. return $hints;
  147. }
  148. /**
  149. * Method the validate question data before storing in the db
  150. * This method will contain basic validation for the question model only
  151. * and will call the validate_attribute method implemented in the subclass
  152. * @param Array $data
  153. */
  154. public function validate($data) {
  155. $question = ORM::factory('question');
  156. $validator = $question->validator($data);
  157. if (!$validator->check() || !$this->validate_attributes($data['attributes'])) {
  158. $this->_validation_errors = array_merge($this->_validation_errors, $validator->errors('question'));
  159. return false;
  160. }
  161. return true;
  162. }
  163. /**
  164. * Method to save the question data in db
  165. * if the question is loaded it is updated
  166. * other wise a new question is added
  167. */
  168. public function save($data) {
  169. if (null === $this->_orm) {
  170. $this->_orm = ORM::factory('question');
  171. }
  172. $hints = $this->process_hints($data['hints']);
  173. $this->_attributes = $this->process_attrs($data['attributes']);
  174. $this->_orm->question = $data['question'];
  175. $this->_orm->extra = Arr::get($data, 'extra', '');
  176. $this->_orm->course_id = Arr::get($data, 'course_id');
  177. $this->_orm->user_id = Auth::instance()->get_user()->id;
  178. $this->_orm->type = $this->_type;
  179. $this->_orm->save();
  180. $this->_orm->delete_all_hints()
  181. ->add_hints($hints)
  182. ->delete_all_attributes()
  183. ->add_attributes($this->_attributes);
  184. }
  185. /**
  186. * Method to show the question
  187. * @param bool $preview optional, default=false
  188. */
  189. public function render_question($preview=false) {
  190. $view = View::factory('question/partial_question')
  191. ->set('preview', $preview)
  192. ->set('idx', $this->_idx != null ? $this->_idx : '')
  193. ->bind('question', $question)
  194. ->bind('has_math_expr', $has_math_expr)
  195. ->bind('answer_template', $answer_template);
  196. $question = $this->_orm;
  197. $hints = $this->_orm->hints_as_array();
  198. $has_math_expr = $this->_orm->has_math();
  199. $answer_template = $this->render_answer_partial();
  200. return $view->render();
  201. }
  202. /**
  203. * Method to get the markup for the answer part of the display
  204. * Each type of question will have a different template so this
  205. * method will have to be implemented by all the subclasses
  206. * @return String html
  207. */
  208. abstract public function render_answer_partial();
  209. /**
  210. * Method to check if the answer to this question is correct
  211. * This will depend upon the type of question and hence implementation
  212. * will be in the subclasses
  213. * @param String $answer
  214. * @return Boolean $result whether correct or not
  215. */
  216. abstract public function check_answer($answer);
  217. /**
  218. * Method to show the answer review which will give an idea to the student
  219. * as to why the answer was correct or wrong and how much marks he/she
  220. * gained/lost
  221. * @param String submitted answer
  222. * @return String Html the view of the answer review in the apt format
  223. */
  224. abstract public function answer_review($submitted_answer);
  225. }