ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.assNumeric.php
Go to the documentation of this file.
1 <?php
2 
19 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20 
37 {
38  protected $lower_limit;
39  protected $upper_limit;
40 
42  public $maxchars;
43 
55  public function __construct(
56  $title = "",
57  $comment = "",
58  $author = "",
59  $owner = -1,
60  $question = ""
61  ) {
63  $this->maxchars = 6;
64  }
65 
71  public function isComplete(): bool
72  {
73  if (
74  strlen($this->title)
75  && $this->author
76  && $this->question
77  && $this->getMaximumPoints() > 0
78  ) {
79  return true;
80  }
81  return false;
82  }
83 
89  public function saveToDb($original_id = ""): void
90  {
91  if ($original_id == "") {
92  $this->saveQuestionDataToDb();
93  } else {
95  }
96 
99  parent::saveToDb($original_id);
100  }
101 
107  public function loadFromDb($question_id): void
108  {
110  global $DIC;
111  $ilDB = $DIC['ilDB'];
112 
113  $result = $ilDB->queryF(
114  "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",
115  array("integer"),
116  array($question_id)
117  );
118  if ($result->numRows() == 1) {
119  $data = $ilDB->fetchAssoc($result);
120  $this->setId($question_id);
121  $this->setObjId($data["obj_fi"]);
122  $this->setTitle((string) $data["title"]);
123  $this->setComment((string) $data["description"]);
124  $this->setNrOfTries($data['nr_of_tries']);
125  $this->setOriginalId($data["original_id"]);
126  $this->setAuthor($data["author"]);
127  $this->setPoints($data["points"]);
128  $this->setOwner($data["owner"]);
129  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data["question_text"], 1));
130  $this->setMaxChars($data["maxnumofchars"]);
131 
132  try {
133  $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
136  }
137 
138  try {
139  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
140  } catch (ilTestQuestionPoolException $e) {
141  }
142  }
143 
144  $result = $ilDB->queryF(
145  "SELECT * FROM qpl_num_range WHERE question_fi = %s ORDER BY aorder ASC",
146  array('integer'),
147  array($question_id)
148  );
149 
150  if ($result->numRows() > 0) {
152  while ($data = $ilDB->fetchAssoc($result)) {
153  $this->setPoints($data['points']);
154  $this->setLowerLimit($data['lowerlimit']);
155  $this->setUpperLimit($data['upperlimit']);
156  }
157  }
158 
159  parent::loadFromDb($question_id);
160  }
161 
173  public function duplicate(bool $for_test = true, string $title = "", string $author = "", int $owner = -1, $testObjId = null): int
174  {
175  if ($this->id <= 0) {
176  // The question has not been saved. It cannot be duplicated
177  return -1;
178  }
179  // duplicate the question in database
180  $this_id = $this->getId();
181  $thisObjId = $this->getObjId();
182 
183  $clone = $this;
184 
185  $original_id = $this->questioninfo->getOriginalId($this->id);
186  $clone->id = -1;
187 
188  if ((int) $testObjId > 0) {
189  $clone->setObjId($testObjId);
190  }
191 
192  if ($title) {
193  $clone->setTitle($title);
194  }
195 
196  if ($author) {
197  $clone->setAuthor($author);
198  }
199  if ($owner) {
200  $clone->setOwner($owner);
201  }
202 
203  if ($for_test) {
204  $clone->saveToDb($original_id);
205  } else {
206  $clone->saveToDb();
207  }
208 
209  // copy question page content
210  $clone->copyPageOfQuestion($this_id);
211  // copy XHTML media objects
212  $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
213 
214  $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
215 
216  return $clone->id;
217  }
218 
227  public function copyObject($target_questionpool_id, $title = "")
228  {
229  if ($this->id <= 0) {
230  // The question has not been saved. It cannot be duplicated
231  return;
232  }
233  // duplicate the question in database
234  $clone = $this;
235 
236  $original_id = $this->questioninfo->getOriginalId($this->id);
237  $clone->id = -1;
238  $source_questionpool_id = $this->getObjId();
239  $clone->setObjId($target_questionpool_id);
240  if ($title) {
241  $clone->setTitle($title);
242  }
243  $clone->saveToDb();
244 
245  // copy question page content
246  $clone->copyPageOfQuestion($original_id);
247  // copy XHTML media objects
248  $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
249 
250  $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
251 
252  return $clone->id;
253  }
254 
255  public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = ""): int
256  {
257  if ($this->getId() <= 0) {
258  throw new RuntimeException('The question has not been saved. It cannot be duplicated');
259  }
260 
261  $sourceQuestionId = $this->id;
262  $sourceParentId = $this->getObjId();
263 
264  // duplicate the question in database
265  $clone = $this;
266  $clone->id = -1;
267 
268  $clone->setObjId($targetParentId);
269 
270  if ($targetQuestionTitle) {
271  $clone->setTitle($targetQuestionTitle);
272  }
273 
274  $clone->saveToDb();
275  // copy question page content
276  $clone->copyPageOfQuestion($sourceQuestionId);
277  // copy XHTML media objects
278  $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
279 
280  $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
281 
282  return $clone->id;
283  }
284 
285  public function getLowerLimit()
286  {
287  return $this->lower_limit;
288  }
289 
290  public function getUpperLimit()
291  {
292  return $this->upper_limit;
293  }
294 
295  public function setLowerLimit($a_limit): void
296  {
297  $a_limit = str_replace(',', '.', $a_limit);
298  $this->lower_limit = $a_limit;
299  }
300 
301  public function setUpperLimit($a_limit): void
302  {
303  $a_limit = str_replace(',', '.', $a_limit);
304  $this->upper_limit = $a_limit;
305  }
306 
312  public function getMaximumPoints(): float
313  {
314  return $this->getPoints();
315  }
316 
318  {
319  $points = 0;
320  if ($this->contains($previewSession->getParticipantsSolution())) {
321  $points = $this->getPoints();
322  }
323 
324  $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $points);
325 
326  return $this->ensureNonNegativePoints($reachedPoints);
327  }
328 
341  public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): float
342  {
343  if ($returndetails) {
344  throw new ilTestException('return details not implemented for ' . __METHOD__);
345  }
346 
348  global $DIC;
349  $ilDB = $DIC['ilDB'];
350 
351  $found_values = array();
352  if (is_null($pass)) {
353  $pass = $this->getSolutionMaxPass($active_id);
354  }
355  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
356  $data = $ilDB->fetchAssoc($result);
357  $enteredvalue = '';
358  if (is_array($data) && array_key_exists('value1', $data)) {
359  $enteredvalue = $data["value1"];
360  }
361 
362  $points = 0;
363  if ($this->contains($enteredvalue)) {
364  $points = $this->getPoints();
365  }
366 
367  return (float)$points;
368  }
369 
380  public function contains($value): bool
381  {
382  $eval = new EvalMath();
383  $eval->suppress_errors = true;
384  $result = $eval->e($value);
385  if (($result === false) || ($result === true)) {
386  return false;
387  }
388 
389  if (($result >= $eval->e($this->getLowerLimit())) && ($result <= $eval->e($this->getUpperLimit()))) {
390  return true;
391  }
392  return false;
393  }
394 
395  protected function isValidNumericSubmitValue($submittedValue): bool
396  {
397  if (is_numeric($submittedValue)) {
398  return true;
399  }
400 
401  if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
402  return true;
403  }
404 
405  return false;
406  }
407 
408  public function validateSolutionSubmit(): bool
409  {
410  if (strlen($this->getSolutionSubmit()) && !$this->isValidNumericSubmitValue($this->getSolutionSubmit())) {
411  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("err_no_numeric_value"), true);
412  return false;
413  }
414 
415  return true;
416  }
417 
418  public function getSolutionSubmit(): string
419  {
420  return trim(str_replace(",", ".", $_POST["numeric_result"]));
421  }
422 
423  public function isValidSolutionSubmit($numeric_solution): bool
424  {
425  $math = new EvalMath();
426  $math->suppress_errors = true;
427  $result = $math->evaluate($numeric_solution);
428 
429  return !(
430  ($result === false || $result === true) && strlen($numeric_solution) > 0
431  );
432  }
433 
442  public function saveWorkingData($active_id, $pass = null, $authorized = true): bool
443  {
445  global $DIC;
446  $ilDB = $DIC['ilDB'];
447 
448  if (is_null($pass)) {
449  $pass = ilObjTest::_getPass($active_id);
450  }
451 
452  $entered_values = 0;
453 
454  $returnvalue = true;
455 
456  $numeric_result = $this->getSolutionSubmit();
457 
458  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $numeric_result, $ilDB, $active_id, $pass, $authorized) {
459  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
460 
461  $update = -1;
462  if ($ilDB->numRows($result) != 0) {
463  $row = $ilDB->fetchAssoc($result);
464  $update = $row["solution_id"];
465  }
466 
467  if ($update != -1) {
468  if (strlen($numeric_result)) {
469  $this->updateCurrentSolution($update, trim($numeric_result), null, $authorized);
470  $entered_values++;
471  } else {
472  $this->removeSolutionRecordById($update);
473  }
474  } else {
475  if (strlen($numeric_result)) {
476  $this->saveCurrentSolution($active_id, $pass, trim($numeric_result), null, $authorized);
477  $entered_values++;
478  }
479  }
480  });
481 
482  if ($entered_values) {
485  $this->lng->txtlng(
486  "assessment",
487  "log_user_entered_values",
489  ),
490  $active_id,
491  $this->getId()
492  );
493  }
494  } else {
497  $this->lng->txtlng(
498  "assessment",
499  "log_user_not_entered_values",
501  ),
502  $active_id,
503  $this->getId()
504  );
505  }
506  }
507 
508  return $returnvalue;
509  }
510 
511  protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
512  {
513  $numericSolution = $this->getSolutionSubmit();
514  $previewSession->setParticipantsSolution($numericSolution);
515  }
516 
517  public function saveAdditionalQuestionDataToDb()
518  {
520  global $DIC;
521  $ilDB = $DIC['ilDB'];
522 
523  // save additional data
524  $ilDB->manipulateF(
525  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
526  array( "integer" ),
527  array( $this->getId() )
528  );
529 
530  $ilDB->manipulateF(
531  "INSERT INTO " . $this->getAdditionalTableName(
532  ) . " (question_fi, maxnumofchars) VALUES (%s, %s)",
533  array( "integer", "integer" ),
534  array(
535  $this->getId(),
536  ($this->getMaxChars()) ? $this->getMaxChars() : 0
537  )
538  );
539  }
540 
541  public function saveAnswerSpecificDataToDb()
542  {
544  global $DIC;
545  $ilDB = $DIC['ilDB'];
546 
547  // Write range to the database
548  $ilDB->manipulateF(
549  "DELETE FROM qpl_num_range WHERE question_fi = %s",
550  array( 'integer' ),
551  array( $this->getId() )
552  );
553 
554  $next_id = $ilDB->nextId('qpl_num_range');
555  $ilDB->manipulateF(
556  "INSERT INTO qpl_num_range (range_id, question_fi, lowerlimit, upperlimit, points, aorder, tstamp)
557  VALUES (%s, %s, %s, %s, %s, %s, %s)",
558  array( 'integer', 'integer', 'text', 'text', 'float', 'integer', 'integer' ),
559  array( $next_id, $this->id, $this->getLowerLimit(), $this->getUpperLimit(
560  ), $this->getPoints(), 0, time() )
561  );
562  }
563 
569  public function getQuestionType(): string
570  {
571  return "assNumeric";
572  }
573 
579  public function getMaxChars()
580  {
581  return $this->maxchars;
582  }
583 
589  public function setMaxChars($maxchars): void
590  {
591  $this->maxchars = $maxchars;
592  }
593 
599  public function getAdditionalTableName(): string
600  {
601  return "qpl_qst_numeric";
602  }
603 
608  public function getRTETextWithMediaObjects(): string
609  {
610  return parent::getRTETextWithMediaObjects();
611  }
612 
616  public function setExportDetailsXLSX(ilAssExcelFormatHelper $worksheet, int $startrow, int $col, int $active_id, int $pass): int
617  {
618  parent::setExportDetailsXLSX($worksheet, $startrow, $col, $active_id, $pass);
619 
620  $solutions = $this->getSolutionValues($active_id, $pass);
621 
622  $i = 1;
623  $worksheet->setCell($startrow + $i, $col, $this->lng->txt("result"));
624  $worksheet->setBold($worksheet->getColumnCoord($col) . ($startrow + $i));
625 
626  $worksheet->setBold($worksheet->getColumnCoord($col) . ($startrow + $i));
627  if (array_key_exists(0, $solutions) &&
628  array_key_exists('value1', $solutions[0]) &&
629  strlen($solutions[0]["value1"])) {
630  $worksheet->setCell($startrow + $i, $col + 2, $solutions[0]["value1"]);
631  }
632  $i++;
633 
634  return $startrow + $i + 1;
635  }
636 
645  public function getOperators($expression): array
646  {
648  }
649 
654  public function getExpressionTypes(): array
655  {
656  return array(
660  );
661  }
662 
671  public function getUserQuestionResult($active_id, $pass): ilUserQuestionResult
672  {
674  global $DIC;
675  $ilDB = $DIC['ilDB'];
676  $result = new ilUserQuestionResult($this, $active_id, $pass);
677 
678  $maxStep = $this->lookupMaxStep($active_id, $pass);
679 
680  if ($maxStep > 0) {
681  $data = $ilDB->queryF(
682  "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
683  array("integer", "integer", "integer","integer"),
684  array($active_id, $pass, $this->getId(), $maxStep)
685  );
686  } else {
687  $data = $ilDB->queryF(
688  "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
689  array("integer", "integer", "integer"),
690  array($active_id, $pass, $this->getId())
691  );
692  }
693 
694  while ($row = $ilDB->fetchAssoc($data)) {
695  $result->addKeyValue(1, $row["value1"]);
696  }
697 
698  $points = $this->calculateReachedPoints($active_id, $pass);
699  $max_points = $this->getMaximumPoints();
700 
701  $result->setReachedPercentage(($points / $max_points) * 100);
702 
703  return $result;
704  }
705 
714  public function getAvailableAnswerOptions($index = null)
715  {
716  return array(
717  "lower" => $this->getLowerLimit(),
718  "upper" => $this->getUpperLimit()
719  );
720  }
721 
722  public function getAnswerTableName()
723  {
724  return '';
725  }
726 
727 }
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
loadFromDb(int $question_id)
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
setNrOfTries(int $a_nr_of_tries)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
__construct( $title="", $comment="", $author="", $owner=-1, $question="")
assNumeric constructor
getExpressionTypes()
Get all available expression types for a specific question.
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
getMaxChars()
Returns the maximum number of characters for the numeric input field.
setLowerLimit($a_limit)
Abstract basic class which is to be extended by the concrete assessment question type classes...
setOwner(int $owner=-1)
saveWorkingData(int $active_id, int $pass, bool $authorized=true)
Saves the learners input of the question to the database.
getColumnCoord(int $a_col)
Get column "name" from number.
ensureNonNegativePoints($points)
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
setCell($a_row, $a_col, $a_value, $datatype=null)
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
getQuestionType()
Returns the question type of the question.
setComment(string $comment="")
isValidNumericSubmitValue($submittedValue)
float $points
The maximum available points for the question.
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
Base Exception for all Exceptions relating to Modules/Test.
setMaxChars($maxchars)
Sets the maximum number of characters for the numeric input field.
updateCurrentSolution(int $solutionId, $value1, $value2, bool $authorized=true)
global $DIC
Definition: feed.php:28
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
setExportDetailsXLSX(ilAssExcelFormatHelper $worksheet, int $startrow, int $col, int $active_id, int $pass)
{}
setBold(string $a_coords)
Set cell(s) to bold.
__construct(VocabulariesInterface $vocabularies)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static logAction(string $logtext, int $active_id, int $question_id)
removeSolutionRecordById(int $solutionId)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
duplicate(bool $for_test=true, string $title="", string $author="", int $owner=-1, $testObjId=null)
Duplicates an assNumericQuestion.
saveToDb($original_id="")
Saves a assNumeric object to a database.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
setPoints(float $points)
setObjId(int $obj_id=0)
string $question
The question text.
getOperators($expression)
Get all available operations for a specific question.
setUpperLimit($a_limit)
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
copyObject($target_questionpool_id, $title="")
Copies an assNumeric object.
isValidSolutionSubmit($numeric_solution)
saveQuestionDataToDb(int $original_id=-1)
getSolutionMaxPass(int $active_id)
isComplete()
Returns true, if a numeric question is complete for use.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setId(int $id=-1)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
setOriginalId(?int $original_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setTitle(string $title="")
setLifecycle(ilAssQuestionLifecycle $lifecycle)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
lookupMaxStep(int $active_id, int $pass)
setAuthor(string $author="")
savePreviewData(ilAssQuestionPreviewSession $previewSession)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
contains($value)
Checks for a given value within the range.
setQuestion(string $question="")