ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.assTextQuestion.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
5 include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
6 include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
7 
22 {
31 
40  var $keywords;
41 
42  var $answers;
43 
50 
51  /* method for automatic string matching */
52  private $matchcondition;
54 
68  function __construct(
69  $title = "",
70  $comment = "",
71  $author = "",
72  $owner = -1,
73  $question = ""
74  )
75  {
77  $this->maxNumOfChars = 0;
78  $this->points = 1;
79  $this->answers = array();
80  $this->matchcondition = 0;
81  }
82 
89  function isComplete()
90  {
91  if (strlen($this->title) and ($this->author) and ($this->question) and ($this->getMaximumPoints() > 0))
92  {
93  return true;
94  }
95  else
96  {
97  return false;
98  }
99  }
100 
107  function saveToDb($original_id = "")
108  {
109  global $ilDB;
110 
112 
113  // save additional data
114  $affectedRows = $ilDB->manipulateF("DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
115  array("integer"),
116  array($this->getId())
117  );
118 
119  $affectedRows = $ilDB->manipulateF("INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, maxnumofchars, keywords, textgap_rating, matchcondition, keyword_relation) VALUES (%s, %s, %s, %s, %s, %s)",
120  array("integer", "integer", "text", "text", 'integer', 'text'),
121  array(
122  $this->getId(),
123  $this->getMaxNumOfChars(),
124  NULL,
125  $this->getTextRating(),
126  $this->matchcondition,
127  $this->getKeywordRelation()
128  )
129  );
130 
131  $ilDB->manipulateF("DELETE FROM qpl_a_essay WHERE question_fi = %s",
132  array("integer"),
133  array($this->getId())
134  );
135 
136  foreach ($this->answers as $answer)
137  {
138  $nextID = $ilDB->nextId('qpl_a_essay');
139  $ilDB->manipulateF("INSERT INTO qpl_a_essay (answer_id, question_fi, answertext, points) VALUES (%s, %s, %s, %s)",
140  array("integer", "integer", "text", 'float'),
141  array(
142  $nextID,
143  $this->getId(),
144  $answer->answertext,
145  $answer->points
146  )
147  );
148  }
149 
151  }
152 
160  function loadFromDb($question_id)
161  {
162  global $ilDB;
163 
164  $result = $ilDB->queryF("SELECT qpl_questions.*, " . $this->getAdditionalTableName() . ".* FROM qpl_questions LEFT JOIN " . $this->getAdditionalTableName() . " ON " . $this->getAdditionalTableName() . ".question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s",
165  array("integer"),
166  array($question_id)
167  );
168  if ($ilDB->numRows($result) == 1)
169  {
170  $data = $ilDB->fetchAssoc($result);
171  $this->setId($question_id);
172  $this->setObjId($data["obj_fi"]);
173  $this->setTitle($data["title"]);
174  $this->setComment($data["description"]);
175  $this->setOriginalId($data["original_id"]);
176  $this->setNrOfTries($data['nr_of_tries']);
177  $this->setAuthor($data["author"]);
178  if(0 != (int)$data["points"])
179  {
180  $this->setPoints($data["points"]);
181  }
182  $this->setOwner($data["owner"]);
183  include_once("./Services/RTE/classes/class.ilRTE.php");
184  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
185  $this->setShuffle($data["shuffle"]);
186  $this->setMaxNumOfChars($data["maxnumofchars"]);
187  $this->setTextRating($this->isValidTextRating($data["textgap_rating"]) ? $data["textgap_rating"] : TEXTGAP_RATING_CASEINSENSITIVE);
188  $this->matchcondition = (strlen($data['matchcondition'])) ? $data['matchcondition'] : 0;
189  $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
190  $this->setKeywordRelation(($data['keyword_relation']));
191  }
192 
193  $result = $ilDB->queryF("SELECT * FROM qpl_a_essay WHERE question_fi = %s",
194  array("integer"),
195  array($this->getId())
196  );
197 
198  $this->flushAnswers();
199  while ($row = $ilDB->fetchAssoc($result))
200  {
201  $this->addAnswer($row['answertext'], $row['points']);
202  }
203 
204  parent::loadFromDb($question_id);
205  }
206 
212  function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
213  {
214  if ($this->id <= 0)
215  {
216  // The question has not been saved. It cannot be duplicated
217  return;
218  }
219  // duplicate the question in database
220  $this_id = $this->getId();
221 
222  if( (int)$testObjId > 0 )
223  {
224  $thisObjId = $this->getObjId();
225  }
226 
227  $clone = $this;
228  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
230  $clone->id = -1;
231 
232  if( (int)$testObjId > 0 )
233  {
234  $clone->setObjId($testObjId);
235  }
236 
237  if ($title)
238  {
239  $clone->setTitle($title);
240  }
241 
242  if ($author)
243  {
244  $clone->setAuthor($author);
245  }
246  if ($owner)
247  {
248  $clone->setOwner($owner);
249  }
250 
251  if ($for_test)
252  {
253  $clone->saveToDb($original_id);
254  }
255  else
256  {
257  $clone->saveToDb();
258  }
259 
260  // copy question page content
261  $clone->copyPageOfQuestion($this_id);
262  // copy XHTML media objects
263  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
264  // duplicate the generic feedback
265  $clone->duplicateGenericFeedback($this_id);
266  #$clone->duplicateAnswers($this_id);
267 
268  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
269 
270  return $clone->id;
271  }
272 
278  function copyObject($target_questionpool, $title = "")
279  {
280  if ($this->id <= 0)
281  {
282  // The question has not been saved. It cannot be duplicated
283  return;
284  }
285  // duplicate the question in database
286  $clone = $this;
287  include_once ("./Modules/TestQuestionPool/classes/class.assQuestion.php");
289  $clone->id = -1;
290  $source_questionpool = $this->getObjId();
291  $clone->setObjId($target_questionpool);
292  if ($title)
293  {
294  $clone->setTitle($title);
295  }
296  $clone->saveToDb();
297 
298  // copy question page content
299  $clone->copyPageOfQuestion($original_id);
300  // copy XHTML media objects
301  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
302  // duplicate the generic feedback
303  $clone->duplicateGenericFeedback($original_id);
304  // duplicate answers
305  #$clone->duplicateAnswers($original_id);
306 
307  $clone->onCopy($this->getObjId(), $this->getId());
308 
309  return $clone->id;
310  }
311 
319  function getMaxNumOfChars()
320  {
321  if (strcmp($this->maxNumOfChars, "") == 0)
322  {
323  return 0;
324  }
325  else
326  {
327  return $this->maxNumOfChars;
328  }
329  }
330 
338  function setMaxNumOfChars($maxchars = 0)
339  {
340  $this->maxNumOfChars = $maxchars;
341  }
342 
349  function getMaximumPoints()
350  {
351  if( in_array($this->getKeywordRelation(), self::getScoringModesWithPointsByQuestion()) )
352  {
353  return parent::getPoints();
354  }
355 
356  $points = 0;
357 
358  foreach ($this->answers as $answer)
359  {
360  if ($answer->points > 0)
361  {
362  $points = $points + $answer->points;
363  }
364  }
365 
366  return $points;
367  }
368 
369  function getMinimumPoints()
370  {
371  if( in_array($this->getKeywordRelation(), self::getScoringModesWithPointsByQuestion()) )
372  {
373  return 0;
374  }
375 
376  $points = 0;
377 
378  foreach ($this->answers as $answer)
379  {
380  if ($answer->points < 0)
381  {
382  $points = $points + $answer->points;
383  }
384  }
385 
386  return $points;
387  }
397  function setReachedPoints($active_id, $points, $pass = NULL)
398  {
399  global $ilDB;
400 
401  if (($points > 0) && ($points <= $this->getPoints()))
402  {
403  if (is_null($pass))
404  {
405  $pass = $this->getSolutionMaxPass($active_id);
406  }
407  $affectedRows = $ilDB->manipulateF("UPDATE tst_test_result SET points = %s WHERE active_fi = %s AND question_fi = %s AND pass = %s",
408  array('float','integer','integer','integer'),
409  array($points, $active_id, $this->getId(), $pass)
410  );
411  $this->_updateTestPassResults($active_id, $pass);
412  return TRUE;
413  }
414  else
415  {
416  return TRUE;
417  }
418  }
419 
420  private function isValidTextRating($textRating)
421  {
422  switch($textRating)
423  {
431  return true;
432  }
433 
434  return false;
435  }
436 
445  function isKeywordMatching($answertext, $a_keyword)
446  {
447  $result = FALSE;
448  $textrating = $this->getTextRating();
449  include_once "./Services/Utilities/classes/class.ilStr.php";
450  switch ($textrating)
451  {
453  if (ilStr::strPos(ilStr::strToLower($answertext), ilStr::strToLower($a_keyword)) !== false) return TRUE;
454  break;
456  if (ilStr::strPos($answertext, $a_keyword) !== false) return TRUE;
457  break;
458  }
459 
460  // "<p>red</p>" would not match "red" even with distance of 5
461  $answertext = strip_tags($answertext);
462 
463  $answerwords = array();
464  if (preg_match_all("/([^\s.]+)/", $answertext, $matches))
465  {
466  foreach ($matches[1] as $answerword)
467  {
468  array_push($answerwords, trim($answerword));
469  }
470  }
471  foreach ($answerwords as $a_original)
472  {
473  switch ($textrating)
474  {
476  if (levenshtein($a_original, $a_keyword) <= 1) return TRUE;
477  break;
479  if (levenshtein($a_original, $a_keyword) <= 2) return TRUE;
480  break;
482  if (levenshtein($a_original, $a_keyword) <= 3) return TRUE;
483  break;
485  if (levenshtein($a_original, $a_keyword) <= 4) return TRUE;
486  break;
488  if (levenshtein($a_original, $a_keyword) <= 5) return TRUE;
489  break;
490  }
491  }
492  return $result;
493  }
494 
505  public function calculateReachedPoints($active_id, $pass = NULL, $returndetails = FALSE)
506  {
507  if( $returndetails )
508  {
509  throw new ilTestException('return details not implemented for '.__METHOD__);
510  }
511 
512  global $ilDB;
513 
514  $points = 0;
515  if (is_null($pass))
516  {
517  $pass = $this->getSolutionMaxPass($active_id);
518  }
519 
520  $result = $ilDB->queryF("SELECT * FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
521  array('integer','integer','integer'),
522  array($active_id, $this->getId(), $pass)
523  );
524 
525  // Return min points when no answer was given.
526  if ($ilDB->numRows($result) == 0)
527  {
528  return $this->getMinimumPoints();
529  }
530 
531  // Return points of points are already on the row.
532  $row = $ilDB->fetchAssoc($result);
533  if ($row["points"] != NULL)
534  {
535  return $row["points"];
536  }
537 
538  // Return min points when keyword relation is NON KEYWORDS
539  if( $this->getKeywordRelation() == 'non' )
540  {
541  return $this->getMinimumPoints();
542  }
543 
544  // Return min points if there are no answers present.
545  $answers = $this->getAnswers();
546 
547  if (count($answers) == 0)
548  {
549  return $this->getMinimumPoints();
550  }
551 
552  switch( $this->getKeywordRelation() )
553  {
554  case 'any':
555 
556  $points = 0;
557 
558  foreach ($answers as $answer)
559  {
560  $qst_answer = $answer->answertext;
561  $user_answer = ' '.$row['value1'];
562 
563  if( $this->isKeywordMatching( $user_answer, $qst_answer ) )
564  {
565  $points += $answer->points;
566  }
567  }
568 
569  break;
570 
571  case 'all':
572 
573  $points = $this->getMaximumPoints();
574 
575  foreach ($answers as $answer)
576  {
577  $qst_answer = $answer->answertext;
578  $user_answer = ' '.$row['value1'];
579 
580  if( !$this->isKeywordMatching( $user_answer, $qst_answer ) )
581  {
582  $points = 0;
583  break;
584  }
585  }
586 
587  break;
588 
589  case 'one':
590 
591  $points = 0;
592 
593  foreach ($answers as $answer)
594  {
595  $qst_answer = $answer->answertext;
596  $user_answer = ' '.$row['value1'];
597 
598  if( $this->isKeywordMatching( $user_answer, $qst_answer ) )
599  {
600  $points = $this->getMaximumPoints();
601  break;
602  }
603  }
604 
605  break;
606  }
607 
608  return $points;
609 
610  }
611 
620  public function saveWorkingData($active_id, $pass = NULL)
621  {
622  global $ilDB;
623  global $ilUser;
624 
625  include_once "./Services/Utilities/classes/class.ilStr.php";
626  if (is_null($pass))
627  {
628  include_once "./Modules/Test/classes/class.ilObjTest.php";
629  $pass = ilObjTest::_getPass($active_id);
630  }
631  $affectedRows = $ilDB->manipulateF("DELETE FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
632  array('integer','integer','integer'),
633  array($active_id, $this->getId(), $pass)
634  );
635  $text = ilUtil::stripSlashes($_POST["TEXT"], FALSE);
636  if ($this->getMaxNumOfChars())
637  {
638  include_once "./Services/Utilities/classes/class.ilStr.php";
639  $text_without_tags = preg_replace("/<[^>*?]>/is", "", $text);
640  $len_with_tags = ilStr::strLen($text);
641  $len_without_tags = ilStr::strLen($text_without_tags);
642  if ($this->getMaxNumOfChars() < $len_without_tags)
643  {
644  if (!$this->isHTML($text))
645  {
646  $text = ilStr::subStr($text, 0, $this->getMaxNumOfChars());
647  }
648  }
649  }
650  if ($this->isHTML($text))
651  {
652  $text = preg_replace("/<[^>]*$/ims", "", $text);
653  }
654  else
655  {
656  //$text = htmlentities($text, ENT_QUOTES, "UTF-8");
657  }
658  $entered_values = 0;
659  if (strlen($text))
660  {
661  $next_id = $ilDB->nextId('tst_solutions');
662  $affectedRows = $ilDB->insert("tst_solutions", array(
663  "solution_id" => array("integer", $next_id),
664  "active_fi" => array("integer", $active_id),
665  "question_fi" => array("integer", $this->getId()),
666  "value1" => array("clob", trim($text)),
667  "value2" => array("clob", null),
668  "pass" => array("integer", $pass),
669  "tstamp" => array("integer", time())
670  ));
671  $entered_values++;
672  }
673  if ($entered_values)
674  {
675  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
677  {
678  $this->logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
679  }
680  }
681  else
682  {
683  include_once ("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
685  {
686  $this->logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
687  }
688  }
689 
690  return true;
691  }
692 
701  protected function reworkWorkingData($active_id, $pass, $obligationsAnswered)
702  {
703  // nothing to rework!
704  }
705 
706  function createRandomSolution($test_id, $user_id)
707  {
708  }
709 
716  function getQuestionType()
717  {
718  return "assTextQuestion";
719  }
720 
728  function getTextRating()
729  {
730  return $this->text_rating;
731  }
732 
740  function setTextRating($a_text_rating)
741  {
742  switch ($a_text_rating)
743  {
751  $this->text_rating = $a_text_rating;
752  break;
753  default:
754  $this->text_rating = TEXTGAP_RATING_CASEINSENSITIVE;
755  break;
756  }
757  }
758 
766  {
767  return "qpl_qst_essay";
768  }
769 
775  {
777  }
778 
791  public function setExportDetailsXLS(&$worksheet, $startrow, $active_id, $pass, &$format_title, &$format_bold)
792  {
793  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
794  $solutions = $this->getSolutionValues($active_id, $pass);
795  $worksheet->writeString($startrow, 0, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())), $format_title);
796  $worksheet->writeString($startrow, 1, ilExcelUtils::_convert_text($this->getTitle()), $format_title);
797  $i = 1;
798  $worksheet->writeString($startrow + $i, 0, ilExcelUtils::_convert_text($this->lng->txt("result")), $format_bold);
799  if (strlen($solutions[0]["value1"]))
800  {
801  $worksheet->write($startrow + $i, 1, ilExcelUtils::_convert_text($solutions[0]["value1"]));
802  }
803  $i++;
804  return $startrow + $i + 1;
805  }
806 
810  public function toJSON()
811  {
812  include_once("./Services/RTE/classes/class.ilRTE.php");
813  $result = array();
814  $result['id'] = (int) $this->getId();
815  $result['type'] = (string) $this->getQuestionType();
816  $result['title'] = (string) $this->getTitle();
817  $result['question'] = $this->formatSAQuestion($this->getQuestion());
818  $result['nr_of_tries'] = (int) $this->getNrOfTries();
819  $result['shuffle'] = (bool) $this->getShuffle();
820  $result['maxlength'] = (int) $this->getMaxNumOfChars();
821  return json_encode($result);
822  }
823 
824  public function getAnswerCount()
825  {
826  return count($this->answers);
827  }
828 
842  public function addAnswer(
843  $answertext = "",
844  $points = 0.0,
845  $points_unchecked = 0.0,
846  $order = 0,
847  $answerimage = ""
848  )
849  {
850  include_once "./Modules/TestQuestionPool/classes/class.assAnswerMultipleResponseImage.php";
851 
852  // add answer
853  $answer = new ASS_AnswerMultipleResponseImage($answertext, $points);
854  $this->answers[] = $answer;
855  }
856 
857  public function getAnswers()
858  {
859  return $this->answers;
860  }
861 
871  function getAnswer($index = 0)
872  {
873  if ($index < 0) return NULL;
874  if (count($this->answers) < 1) return NULL;
875  if ($index >= count($this->answers)) return NULL;
876 
877  return $this->answers[$index];
878  }
879 
888  function deleteAnswer($index = 0)
889  {
890  if ($index < 0) return;
891  if (count($this->answers) < 1) return;
892  if ($index >= count($this->answers)) return;
893  $answer = $this->answers[$index];
894  if (strlen($answer->getImage())) $this->deleteImage($answer->getImage());
895  unset($this->answers[$index]);
896  $this->answers = array_values($this->answers);
897  for ($i = 0; $i < count($this->answers); $i++)
898  {
899  if ($this->answers[$i]->getOrder() > $index)
900  {
901  $this->answers[$i]->setOrder($i);
902  }
903  }
904  }
905 
907  {
908  return 'qpl_a_essay';
909  }
910 
917  function flushAnswers()
918  {
919  $this->answers = array();
920  }
921 
922  public function setAnswers($answers)
923  {
924  if( isset($answers['answer']) )
925  {
926  $count = count($answers['answer']);
927  $withPoints = true;
928  }
929  else
930  {
931  $count = count($answers);
932  $withPoints = false;
933  }
934 
935  $this->flushAnswers();
936 
937  for( $i = 0; $i < $count; $i++ )
938  {
939  if($withPoints)
940  {
941  $this->addAnswer($answers['answer'][$i], $answers['points'][$i]);
942  }
943  else
944  {
945  $this->addAnswer($answers[$i], 0);
946  }
947  }
948  }
949 
951  {
952  global $ilDB;
953 
954  $result = $ilDB->queryF("SELECT * FROM qpl_a_essay WHERE question_fi = %s",
955  array('integer'),
956  array($original_id)
957  );
958  if ($result->numRows())
959  {
960  while ($row = $ilDB->fetchAssoc($result))
961  {
962  $next_id = $ilDB->nextId('qpl_a_essay');
963  $affectedRows = $ilDB->manipulateF(
964  "INSERT INTO qpl_a_essay (answer_id, question_fi, answertext, points)
965  VALUES (%s, %s, %s, %s)",
966  array('integer','integer','text','integer'),
967  array($next_id, $this->getId(), $row["answertext"], $row["points"])
968  );
969  }
970  }
971  }
972 
973  public function getKeywordRelation()
974  {
976  }
977 
982  public function setKeywordRelation($a_relation)
983  {
984  $this->keyword_relation = $a_relation;
985  }
993  function saveFeedbackSingleAnswer($answer_index, $feedback)
994  {
995  global $ilDB;
996 
997  $affectedRows = $ilDB->manipulateF("DELETE FROM qpl_fb_essay WHERE question_fi = %s AND answer = %s",
998  array('integer','integer'),
999  array($this->getId(), $answer_index)
1000  );
1001  if (strlen($feedback))
1002  {
1003  include_once("./Services/RTE/classes/class.ilRTE.php");
1004  $next_id = $ilDB->nextId('qpl_fb_essay');
1006  $ilDB->insert('qpl_fb_essay', array(
1007  'feedback_id' => array( 'integer', $next_id ),
1008  'question_fi' => array( 'integer', $this->getId() ),
1009  'answer' => array( 'integer', $answer_index ),
1010  'feedback' => array( 'clob', ilRTE::_replaceMediaObjectImageSrc($feedback, 0) ),
1011  'tstamp' => array( 'integer', time() ),
1012  )
1013  );
1014  }
1015  }
1016 
1024  function getFeedbackSingleAnswer($answer_index)
1025  {
1026  global $ilDB;
1027 
1028  $feedback = "";
1029  $result = $ilDB->queryF("SELECT * FROM qpl_fb_essay WHERE question_fi = %s AND answer = %s",
1030  array('integer','integer'),
1031  array($this->getId(), $answer_index)
1032  );
1033  if ($result->numRows())
1034  {
1035  $row = $ilDB->fetchAssoc($result);
1036  include_once("./Services/RTE/classes/class.ilRTE.php");
1037  $feedback = ilRTE::_replaceMediaObjectImageSrc($row["feedback"], 1);
1038  }
1039  return $feedback;
1040  }
1041 
1042  protected function deleteFeedbackSpecific($question_id)
1043  {
1044  global $ilDB;
1045  $ilDB->manipulateF(
1046  'DELETE
1047  FROM qpl_fb_essay
1048  WHERE question_fi = %s',
1049  array('integer'),
1050  array($question_id)
1051  );
1052  }
1053 
1054  public static function getValidScoringModes()
1055  {
1056  return array_merge(self::getScoringModesWithPointsByQuestion(), self::getScoringModesWithPointsByKeyword());
1057  }
1058 
1059  public static function getScoringModesWithPointsByQuestion()
1060  {
1061  return array('non', 'all', 'one');
1062  }
1063 
1064  public static function getScoringModesWithPointsByKeyword()
1065  {
1066  return array('any');
1067  }
1068 
1069 
1076  function duplicateSpecificFeedback($original_id)
1077  {
1078  global $ilDB;
1079 
1080  $feedback = "";
1081  $result = $ilDB->queryF("SELECT * FROM qpl_fb_essay WHERE question_fi = %s",
1082  array('integer'),
1083  array($original_id)
1084  );
1085  if ($result->numRows())
1086  {
1087  while ($row = $ilDB->fetchAssoc($result))
1088  {
1089  $next_id = $ilDB->nextId('qpl_fb_essay');
1091  $ilDB->insert('qpl_fb_essay', array(
1092  'feedback_id' => array( 'integer', $next_id ),
1093  'question_fi' => array( 'integer', $this->getId() ),
1094  'answer' => array( 'integer', $row["answer"] ),
1095  'feedback' => array( 'clob', $row["feedback"] ),
1096  'tstamp' => array( 'integer', time() ),
1097  )
1098  );
1099  }
1100  }
1101  }
1102 
1113  public function isAnswered($active_id, $pass)
1114  {
1115  $answered = assQuestion::doesSolutionRecordsExist($active_id, $pass, $this->getId());
1116 
1117  return $answered;
1118  }
1119 
1130  public static function isObligationPossible($questionId)
1131  {
1132  return true;
1133  }
1134 }