ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.assNumeric.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
23 
40 {
41  protected $lower_limit;
42  protected $upper_limit;
43  public $maxchars = 6;
44 
45  public function isComplete(): bool
46  {
47  if (
48  strlen($this->title)
49  && $this->author
50  && $this->question
51  && $this->getMaximumPoints() > 0
52  ) {
53  return true;
54  }
55  return false;
56  }
57 
58  public function saveToDb(?int $original_id = null): void
59  {
63  parent::saveToDb($original_id);
64  }
65 
66  public function loadFromDb(int $question_id): void
67  {
68  $result = $this->db->queryF(
69  "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",
70  ["integer"],
71  [$question_id]
72  );
73  if ($result->numRows() == 1) {
74  $data = $this->db->fetchAssoc($result);
75  $this->setId($question_id);
76  $this->setObjId($data["obj_fi"]);
77  $this->setTitle((string) $data["title"]);
78  $this->setComment((string) $data["description"]);
79  $this->setNrOfTries($data['nr_of_tries']);
80  $this->setOriginalId($data["original_id"]);
81  $this->setAuthor($data["author"]);
82  $this->setPoints($data["points"]);
83  $this->setOwner($data["owner"]);
84  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data["question_text"], 1));
85  $this->setMaxChars($data["maxnumofchars"]);
86 
87  try {
88  $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
91  }
92 
93  try {
94  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
95  } catch (ilTestQuestionPoolException $e) {
96  }
97  }
98 
99  $result = $this->db->queryF(
100  "SELECT * FROM qpl_num_range WHERE question_fi = %s ORDER BY aorder ASC",
101  ['integer'],
102  [$question_id]
103  );
104 
105  if ($result->numRows() > 0) {
107  while ($data = $this->db->fetchAssoc($result)) {
108  $this->setPoints($data['points']);
109  $this->setLowerLimit($data['lowerlimit']);
110  $this->setUpperLimit($data['upperlimit']);
111  }
112  }
113 
114  parent::loadFromDb($question_id);
115  }
116 
117  public function getLowerLimit()
118  {
119  return $this->lower_limit;
120  }
121 
122  public function getUpperLimit()
123  {
124  return $this->upper_limit;
125  }
126 
127  public function setLowerLimit(string $limit): void
128  {
129  $this->lower_limit = str_replace(',', '.', $limit);
130  }
131 
132  public function setUpperLimit(string $limit): void
133  {
134  $this->upper_limit = str_replace(',', '.', $limit);
135  }
136 
137  public function getMaximumPoints(): float
138  {
139  return $this->getPoints();
140  }
141 
143  {
144  $points = 0;
145  if ($this->contains($previewSession->getParticipantsSolution())) {
146  $points = $this->getPoints();
147  }
148 
149  return $this->ensureNonNegativePoints($points);
150  }
151 
152  public function calculateReachedPoints(
153  int $active_id,
154  ?int $pass = null,
155  bool $authorized_solution = true
156  ): float {
157  if ($pass === null) {
158  $pass = $this->getSolutionMaxPass($active_id);
159  }
160  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
161  $data = $this->db->fetchAssoc($result);
162  $enteredvalue = '';
163  if (is_array($data) && array_key_exists('value1', $data)) {
164  $enteredvalue = $data["value1"];
165  }
166 
167  $points = 0.0;
168  if ($this->contains($enteredvalue)) {
169  $points = $this->getPoints();
170  }
171 
172  return $points;
173  }
174 
185  public function contains($value): bool
186  {
187  $eval = new EvalMath();
188  $eval->suppress_errors = true;
189  $result = $eval->e((string) $value);
190  if (($result === false) || ($result === true)) {
191  return false;
192  }
193 
194  if (($result >= $eval->e($this->getLowerLimit())) && ($result <= $eval->e($this->getUpperLimit()))) {
195  return true;
196  }
197  return false;
198  }
199 
200  public function validateSolutionSubmit(): bool
201  {
202  if ($this->getSolutionSubmit() === null) {
203  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("err_no_numeric_value"), true);
204  return false;
205  }
206 
207  return true;
208  }
209 
210  protected function getSolutionSubmit(): ?float
211  {
212  return $this->questionpool_request->float('numeric_result') ?? null;
213  }
214 
215  public function isValidSolutionSubmit($numeric_solution): bool
216  {
217  $math = new EvalMath();
218  $math->suppress_errors = true;
219  $result = $math->evaluate($numeric_solution);
220 
221  return !(
222  ($result === false || $result === true) && strlen($numeric_solution) > 0
223  );
224  }
225 
226  public function saveWorkingData(
227  int $active_id,
228  ?int $pass = null,
229  bool $authorized = true
230  ): bool {
231  if (is_null($pass)) {
232  $pass = ilObjTest::_getPass($active_id);
233  }
234 
235  $answer = $this->getSolutionSubmit();
236  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
237  function () use ($answer, $active_id, $pass, $authorized) {
238  $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
239  $update = -1;
240  if ($this->db->numRows($result) !== 0) {
241  $row = $this->db->fetchAssoc($result);
242  $update = $row['solution_id'];
243  }
244 
245  if ($update !== -1
246  && $answer === '') {
247  $this->removeSolutionRecordById($update);
248  return;
249  }
250  if ($update !== -1) {
251  $this->updateCurrentSolution($update, $answer, null, $authorized);
252  return;
253  }
254 
255  if ($answer !== '') {
256  $this->saveCurrentSolution($active_id, $pass, $answer, null, $authorized);
257  }
258  }
259  );
260 
261  return true;
262  }
263 
264  protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
265  {
266  $numericSolution = $this->getSolutionSubmit();
267  $previewSession->setParticipantsSolution($numericSolution);
268  }
269 
271  {
272  // save additional data
273  $this->db->manipulateF(
274  "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
275  [ "integer" ],
276  [ $this->getId() ]
277  );
278 
279  $this->db->manipulateF(
280  "INSERT INTO " . $this->getAdditionalTableName(
281  ) . " (question_fi, maxnumofchars) VALUES (%s, %s)",
282  [ "integer", "integer" ],
283  [
284  $this->getId(),
285  ($this->getMaxChars()) ? $this->getMaxChars() : 0
286  ]
287  );
288  }
289 
290  public function saveAnswerSpecificDataToDb()
291  {
292  // Write range to the database
293  $this->db->manipulateF(
294  "DELETE FROM qpl_num_range WHERE question_fi = %s",
295  [ 'integer' ],
296  [ $this->getId() ]
297  );
298 
299  $next_id = $this->db->nextId('qpl_num_range');
300  $this->db->manipulateF(
301  "INSERT INTO qpl_num_range (range_id, question_fi, lowerlimit, upperlimit, points, aorder, tstamp)
302  VALUES (%s, %s, %s, %s, %s, %s, %s)",
303  [ 'integer', 'integer', 'text', 'text', 'float', 'integer', 'integer' ],
304  [ $next_id, $this->id, $this->getLowerLimit(), $this->getUpperLimit(
305  ), $this->getPoints(), 0, time() ]
306  );
307  }
308 
314  public function getQuestionType(): string
315  {
316  return "assNumeric";
317  }
318 
324  public function getMaxChars()
325  {
326  return $this->maxchars;
327  }
328 
334  public function setMaxChars($maxchars): void
335  {
336  $this->maxchars = $maxchars;
337  }
338 
344  public function getAdditionalTableName(): string
345  {
346  return "qpl_qst_numeric";
347  }
348 
353  public function getRTETextWithMediaObjects(): string
354  {
355  return parent::getRTETextWithMediaObjects();
356  }
357 
358  public function getOperators(string $expression): array
359  {
360  return ilOperatorsExpressionMapping::getOperatorsByExpression($expression);
361  }
362 
363  public function getExpressionTypes(): array
364  {
365  return [
369  ];
370  }
371 
372  public function getUserQuestionResult(
373  int $active_id,
374  int $pass
376  $result = new ilUserQuestionResult($this, $active_id, $pass);
377 
378  $maxStep = $this->lookupMaxStep($active_id, $pass);
379  if ($maxStep > 0) {
380  $data = $this->db->queryF(
381  "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
382  ["integer", "integer", "integer","integer"],
383  [$active_id, $pass, $this->getId(), $maxStep]
384  );
385  } else {
386  $data = $this->db->queryF(
387  "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
388  ["integer", "integer", "integer"],
389  [$active_id, $pass, $this->getId()]
390  );
391  }
392 
393  while ($row = $this->db->fetchAssoc($data)) {
394  $result->addKeyValue(1, $row["value1"]);
395  }
396 
397  $points = $this->calculateReachedPoints($active_id, $pass);
398  $max_points = $this->getMaximumPoints();
399 
400  $result->setReachedPercentage(($points / $max_points) * 100);
401 
402  return $result;
403  }
404 
405  public function getAvailableAnswerOptions(?int $index = null): array
406  {
407  return [
408  "lower" => $this->getLowerLimit(),
409  "upper" => $this->getUpperLimit()
410  ];
411  }
412 
413  public function getAnswerTableName(): string
414  {
415  return '';
416  }
417 
418  public function toLog(AdditionalInformationGenerator $additional_info): array
419  {
420  return [
421  AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
422  AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
423  AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
424  AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
426  AdditionalInformationGenerator::KEY_QUESTION_MAXCHARS => $this->getMaxChars(),
427  AdditionalInformationGenerator::KEY_QUESTION_REACHABLE_POINTS => $this->getPoints(),
428  AdditionalInformationGenerator::KEY_QUESTION_LOWER_LIMIT => $this->getLowerLimit(),
429  AdditionalInformationGenerator::KEY_QUESTION_UPPER_LIMIT => $this->getUpperLimit(),
430  AdditionalInformationGenerator::KEY_FEEDBACK => [
431  AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
432  AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
433  ]
434  ];
435  }
436 
437  protected function solutionValuesToLog(
438  AdditionalInformationGenerator $additional_info,
439  array $solution_values
440  ): string {
441  if (!array_key_exists(0, $solution_values) ||
442  !array_key_exists('value1', $solution_values[0])) {
443  return '';
444  }
445  return $solution_values[0]['value1'];
446  }
447 
448  public function solutionValuesToText(array $solution_values): string
449  {
450  if (!array_key_exists(0, $solution_values) ||
451  !array_key_exists('value1', $solution_values[0])) {
452  return '';
453  }
454  return $solution_values[0]['value1'];
455  }
456 
457  public function getCorrectSolutionForTextOutput(int $active_id, int $pass): string
458  {
459  return "{$this->getLowerLimit()}-{$this->getUpperLimit()}";
460  }
461 }
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...
setNrOfTries(int $a_nr_of_tries)
solutionValuesToText(array $solution_values)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
loadFromDb(int $question_id)
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
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.
getExpressionTypes()
Get all available expression types for a specific question.
getMaxChars()
Returns the maximum number of characters for the numeric input field.
setOwner(int $owner=-1)
ensureNonNegativePoints(float $points)
setLowerLimit(string $limit)
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
getQuestionType()
Returns the question type of the question.
setComment(string $comment="")
setMaxChars($maxchars)
Sets the maximum number of characters for the numeric input field.
updateCurrentSolution(int $solutionId, $value1, $value2, bool $authorized=true)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
getAvailableAnswerOptions(?int $index=null)
If index is null, the function returns an array with all anwser options else it returns the specific ...
getCorrectSolutionForTextOutput(int $active_id, int $pass)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
removeSolutionRecordById(int $solutionId)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
setPoints(float $points)
setObjId(int $obj_id=0)
saveQuestionDataToDb(?int $original_id=null)
saveToDb(?int $original_id=null)
isValidSolutionSubmit($numeric_solution)
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
getSolutionMaxPass(int $active_id)
toLog(AdditionalInformationGenerator $additional_info)
Class for numeric questions.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setId(int $id=-1)
setUpperLimit(string $limit)
setOriginalId(?int $original_id)
setTitle(string $title="")
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getOperators(string $expression)
Get all available operations for a specific question.
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.
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
setQuestion(string $question="")