ILIAS  trunk Revision v11.0_alpha-1753-gb21ca8c4367
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilTestSession.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
31 {
32  public const ACCESS_CODE_SESSION_INDEX = "tst_access_code";
33  public const ACCESS_CODE_CHAR_DOMAIN = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
34  public const ACCESS_CODE_LENGTH = 5;
35 
36  private int $ref_id = 0;
37  private int $pass = 0;
38  public int $active_id = 0;
39  public int $user_id = 0;
40  public string $anonymous_id = '';
41  public int $test_id = 0;
42  public int $lastsequence = 0;
43  protected ?string $lastPresentationMode = null;
44  public bool $submitted = false;
45  public int $tstamp = 0;
46  public string $submittedTimestamp = '';
48 
49  private ?int $lastFinishedPass = null;
50  private ?int $lastStartedPass = null;
51 
52 
61  public function __construct(
62  protected ilDBInterface $db,
63  protected ilObjUser $user
64  ) {
65  }
66 
67  public function setRefId(int $ref_id): void
68  {
69  $this->ref_id = $ref_id;
70  }
71 
72  public function getRefId(): int
73  {
74  return $this->ref_id;
75  }
76 
77  protected function activeIDExists(int $user_id, int $test_id): bool
78  {
79  if ($this->user->getId() === ANONYMOUS_USER_ID) {
80  return false;
81  }
82 
83  $result = $this->db->queryF(
84  "SELECT * FROM tst_active WHERE user_fi = %s AND test_fi = %s",
85  ['integer','integer'],
86  [$user_id, $test_id]
87  );
88  if ($result->numRows()) {
89  $row = $this->db->fetchAssoc($result);
90  $this->active_id = (int) $row["active_id"];
91  $this->user_id = (int) $row["user_fi"];
92  $this->anonymous_id = $row["anonymous_id"] ?? '';
93  $this->test_id = (int) $row["test_fi"];
94  $this->lastsequence = (int) $row["lastindex"];
95  $this->pass = (int) $row["tries"];
96  $this->submitted = ($row["submitted"]) ? true : false;
97  $this->submittedTimestamp = (string) $row["submittimestamp"];
98  $this->tstamp = (int) $row["tstamp"];
99 
100  $this->lastStartedPass = $row['last_started_pass'];
101  $this->lastFinishedPass = $row['last_finished_pass'];
102  $this->objectiveOrientedContainerId = $row['objective_container'];
103 
104  return true;
105  }
106  return false;
107  }
108 
109  public function increaseTestPass(): void
110  {
111  if (!$this->active_id) {
112  throw new ilTestException('missing active id on test pass increase!');
113  }
114 
115  $this->increasePass();
116  $this->setLastSequence(0);
117  $submitted = ($this->isSubmitted()) ? 1 : 0;
118  $active = ilSession::get((string) $this->active_id);
119  if (!isset($active['tst_last_increase_pass']) || $active['tst_last_increase_pass'] !== null) {
120  $active['tst_last_increase_pass'] = 0;
121  }
122 
123  // there has to be at least 10 seconds between new test passes (to ensure that noone double clicks the finish button and increases the test pass by more than 1)
124  if (time() - $active['tst_last_increase_pass'] > 10) {
125  $active['tst_last_increase_pass'] = time();
126  $this->tstamp = time();
127  $submittedtimestamp = $this->getSubmittedTimestamp() !== null && $this->getSubmittedTimestamp() !== '' ? $this->getSubmittedTimestamp() : null;
128  $this->db->update(
129  'tst_active',
130  [
131  'lastindex' => ['integer', $this->getLastSequence()],
132  'tries' => ['integer', $this->getPass()],
133  'submitted' => ['integer', $submitted],
134  'submittimestamp' => ['timestamp', $submittedtimestamp],
135  'tstamp' => ['integer', time()],
136  'last_finished_pass' => ['integer', $this->getLastFinishedPass()],
137  'last_started_pass' => ['integer', $this->getLastStartedPass()],
138  'objective_container' => ['integer', $this->getObjectiveOrientedContainerId()]
139  ],
140  [
141  'active_id' => ['integer', $this->getActiveId()]
142  ]
143  );
144  }
145  }
146 
147  public function saveToDb(): void
148  {
149  $submitted = ($this->isSubmitted()) ? 1 : 0;
150  $submittedtimestamp = $this->getSubmittedTimestamp() !== null && $this->getSubmittedTimestamp() !== '' ? $this->getSubmittedTimestamp() : null;
151  if ($this->active_id > 0) {
152  $this->db->update(
153  'tst_active',
154  [
155  'lastindex' => ['integer', $this->getLastSequence()],
156  'tries' => ['integer', $this->getPass()],
157  'submitted' => ['integer', $submitted],
158  'submittimestamp' => ['timestamp', $submittedtimestamp],
159  'tstamp' => ['integer', time() - 10],
160  'last_finished_pass' => ['integer', $this->getLastFinishedPass()],
161  'last_started_pass' => ['integer', $this->getPass()],
162  'objective_container' => ['integer', $this->getObjectiveOrientedContainerId()]
163  ],
164  [
165  'active_id' => ['integer', $this->getActiveId()]
166  ]
167  );
168  return;
169  }
170 
171  if (!$this->activeIDExists($this->getUserId(), $this->getTestId())) {
172  $anonymous_id = $this->getAnonymousId() ?: null;
173 
174  $next_id = $this->db->nextId('tst_active');
175  $this->db->insert(
176  'tst_active',
177  [
178  'active_id' => ['integer', $next_id],
179  'user_fi' => ['integer', $this->getUserId()],
180  'anonymous_id' => ['text', $anonymous_id],
181  'test_fi' => ['integer', $this->getTestId()],
182  'lastindex' => ['integer', $this->getLastSequence()],
183  'tries' => ['integer', $this->getPass()],
184  'submitted' => ['integer', $submitted],
185  'submittimestamp' => ['timestamp', (strlen($this->getSubmittedTimestamp())) ? $this->getSubmittedTimestamp() : null],
186  'tstamp' => ['integer', time() - 10],
187  'last_finished_pass' => ['integer', $this->getLastFinishedPass()],
188  'last_started_pass' => ['integer', $this->getPass()],
189  'objective_container' => ['integer', $this->getObjectiveOrientedContainerId()]
190  ]
191  );
192  $this->active_id = $next_id;
193  }
194  }
195 
196  public function loadTestSession(int $test_id, int $user_id = 0, ?string $anonymous_id = null): void
197  {
198  if ($user_id === 0) {
199  $user_id = $this->user->getId();
200  }
201  if (($this->user->getId() == ANONYMOUS_USER_ID) && $this->doesAccessCodeInSessionExists()) {
202  $result = $this->db->queryF(
203  "SELECT * FROM tst_active WHERE user_fi = %s AND test_fi = %s AND anonymous_id = %s",
204  ['integer','integer','text'],
205  [$user_id, $test_id, $this->getAccessCodeFromSession()]
206  );
207  } elseif ($anonymous_id !== null) {
208  $result = $this->db->queryF(
209  "SELECT * FROM tst_active WHERE user_fi = %s AND test_fi = %s AND anonymous_id = %s",
210  ['integer','integer','text'],
211  [$user_id, $test_id, $anonymous_id]
212  );
213  } else {
214  if ($this->user->getId() == ANONYMOUS_USER_ID) {
215  return;
216  }
217  $result = $this->db->queryF(
218  "SELECT * FROM tst_active WHERE user_fi = %s AND test_fi = %s",
219  ['integer','integer'],
220  [$user_id, $test_id]
221  );
222  }
223 
224  // TODO bheyser: Refactor
225  $this->user_id = $user_id;
226 
227  if ($result->numRows()) {
228  $row = $this->db->fetchAssoc($result);
229  $this->active_id = $row["active_id"];
230  $this->user_id = $row["user_fi"];
231  $this->anonymous_id = $row["anonymous_id"] ?? '';
232  $this->test_id = $row["test_fi"];
233  $this->lastsequence = $row["lastindex"];
234  $this->pass = $row["tries"];
235  $this->submitted = ($row["submitted"]) ? true : false;
236  $this->submittedTimestamp = $row["submittimestamp"] ?? '';
237  $this->tstamp = $row["tstamp"];
238  $this->lastStartedPass = $row['last_started_pass'];
239  $this->lastFinishedPass = $row['last_finished_pass'];
240  $this->setObjectiveOrientedContainerId((int) $row['objective_container']);
241  } elseif ($this->doesAccessCodeInSessionExists()) {
242  $this->unsetAccessCodeInSession();
243  }
244  }
245 
246  public function loadFromDb(int $active_id): void
247  {
248  $result = $this->db->queryF(
249  "SELECT * FROM tst_active WHERE active_id = %s",
250  ['integer'],
251  [$active_id]
252  );
253  if ($result->numRows()) {
254  $row = $this->db->fetchAssoc($result);
255  $this->active_id = $row["active_id"];
256  $this->user_id = $row["user_fi"];
257  $this->anonymous_id = $row["anonymous_id"] ?? '';
258  $this->test_id = $row["test_fi"];
259  $this->lastsequence = $row["lastindex"];
260  $this->pass = $row["tries"];
261  $this->submitted = ($row["submitted"]) ? true : false;
262  $this->submittedTimestamp = $row["submittimestamp"] ?? '';
263  $this->tstamp = $row["tstamp"];
264 
265  $this->lastStartedPass = $row['last_started_pass'];
266  $this->lastFinishedPass = $row['last_finished_pass'];
267  $this->setObjectiveOrientedContainerId((int) $row['objective_container']);
268  }
269  }
270 
271  public function getActiveId(): int
272  {
273  return $this->active_id;
274  }
275 
276  public function setUserId(int $user_id): void
277  {
278  $this->user_id = $user_id;
279  }
280 
281  public function getUserId(): int
282  {
283  return $this->user_id;
284  }
285 
286  public function setTestId(int $test_id): void
287  {
288  $this->test_id = $test_id;
289  }
290 
291  public function getTestId(): int
292  {
293  return $this->test_id;
294  }
295 
296  public function setAnonymousId(string $anonymous_id): void
297  {
298  $this->anonymous_id = $anonymous_id;
299  }
300 
301  public function getAnonymousId(): string
302  {
303  return $this->anonymous_id;
304  }
305 
306  public function setLastSequence(int $lastsequence): void
307  {
308  $this->lastsequence = $lastsequence;
309  }
310 
311  public function getLastSequence(): int
312  {
313  return $this->lastsequence;
314  }
315 
316  public function setPass(int $pass): void
317  {
318  $this->pass = $pass;
319  }
320 
321  public function getPass(): int
322  {
323  return $this->pass;
324  }
325 
326  public function increasePass()
327  {
328  $this->pass += 1;
329  }
330 
331  public function isSubmitted(): bool
332  {
333  return $this->submitted;
334  }
335 
336  public function setSubmitted(): void
337  {
338  $this->submitted = true;
339  }
340 
341  public function getSubmittedTimestamp(): ?string
342  {
344  }
345 
346  public function setSubmittedTimestamp(): void
347  {
348  $this->submittedTimestamp = date('Y-m-d H:i:s');
349  }
350 
351  public function setLastFinishedPass(int $lastFinishedPass): void
352  {
353  $this->lastFinishedPass = $lastFinishedPass;
354  }
355 
356  public function getLastFinishedPass(): ?int
357  {
359  }
360 
361  public function setObjectiveOrientedContainerId(int $objectiveOriented): void
362  {
363  $this->objectiveOrientedContainerId = $objectiveOriented;
364  }
365 
367  {
368  return $this->objectiveOrientedContainerId ?? 0;
369  }
370 
374  public function getLastStartedPass(): ?int
375  {
376  return $this->lastStartedPass;
377  }
378 
379  public function setLastStartedPass(int $lastStartedPass): void
380  {
381  $this->lastStartedPass = $lastStartedPass;
382  }
383 
384  public function isObjectiveOriented(): bool
385  {
386  return (bool) $this->getObjectiveOrientedContainerId();
387  }
388 
389  public function persistTestStartLock(string $testStartLock): void
390  {
391  $this->db->update(
392  'tst_active',
393  ['start_lock' => ['text', $testStartLock]],
394  ['active_id' => ['integer', $this->getActiveId()]]
395  );
396  }
397 
398  public function lookupTestStartLock(): ?string
399  {
400  $res = $this->db->queryF(
401  "SELECT start_lock FROM tst_active WHERE active_id = %s",
402  ['integer'],
403  [$this->getActiveId()]
404  );
405 
406  while ($row = $this->db->fetchAssoc($res)) {
407  return $row['start_lock'];
408  }
409 
410  return null;
411  }
412 
413  public function setAccessCodeToSession(string $access_code): void
414  {
415  if (!is_array(ilSession::get(self::ACCESS_CODE_SESSION_INDEX))) {
416  ilSession::set(self::ACCESS_CODE_SESSION_INDEX, []);
417  }
418  $session_code = ilSession::get(self::ACCESS_CODE_SESSION_INDEX);
419  $session_code[$this->getTestId()] = $access_code;
420  ilSession::set(self::ACCESS_CODE_SESSION_INDEX, $session_code);
421  }
422 
423  public function unsetAccessCodeInSession(): void
424  {
425  $session_code = ilSession::get(self::ACCESS_CODE_SESSION_INDEX);
426  unset($session_code[$this->getTestId()]);
427  ilSession::set(self::ACCESS_CODE_SESSION_INDEX, $session_code);
428  }
429 
430  public function getAccessCodeFromSession(): ?string
431  {
432  if (!is_array(ilSession::get(self::ACCESS_CODE_SESSION_INDEX))) {
433  return null;
434  }
435  $session_code = ilSession::get(self::ACCESS_CODE_SESSION_INDEX);
436  if (!isset($session_code[$this->getTestId()])) {
437  return null;
438  }
439 
440  return $session_code[$this->getTestId()];
441  }
442 
443  public function doesAccessCodeInSessionExists(): bool
444  {
445  return is_array(ilSession::get(self::ACCESS_CODE_SESSION_INDEX)) && isset(ilSession::get(self::ACCESS_CODE_SESSION_INDEX)[$this->getTestId()]);
446  }
447 
448  public function createNewAccessCode(): string
449  {
450  do {
451  $code = $this->buildAccessCode();
452  } while ($this->isAccessCodeUsed($code));
453 
454  return $code;
455  }
456 
457  public function isAccessCodeUsed(string $code): bool
458  {
459  $query = "SELECT anonymous_id FROM tst_active WHERE test_fi = %s AND anonymous_id = %s";
460 
461  $result = $this->db->queryF(
462  $query,
463  ['integer', 'text'],
464  [$this->getTestId(), $code]
465  );
466 
467  return ($result->numRows() > 0);
468  }
469 
470  private function buildAccessCode(): string
471  {
472  // create a 5 character code
473  $codestring = self::ACCESS_CODE_CHAR_DOMAIN;
474 
475  mt_srand();
476 
477  $code = "";
478 
479  for ($i = 1; $i <= self::ACCESS_CODE_LENGTH; $i++) {
480  $index = mt_rand(0, strlen($codestring) - 1);
481  $code .= substr($codestring, $index, 1);
482  }
483 
484  return $code;
485  }
486 
487  public function isAnonymousUser(): bool
488  {
489  return $this->getUserId() == ANONYMOUS_USER_ID;
490  }
491 
492  public function isPasswordChecked(): bool
493  {
494  if (ilSession::get('pw_checked_' . $this->active_id) === null) {
495  return false;
496  }
497  return ilSession::get('pw_checked_' . $this->active_id);
498  }
499 
500  public function setPasswordChecked(bool $value): void
501  {
502  ilSession::set('pw_checked_' . $this->active_id, $value);
503  }
504 
506 
507  public function reportableResultsAvailable(ilObjTest $test_obj): ?bool
508  {
509  if ($this->reportable_results_available === null) {
510  $this->reportable_results_available = true;
511 
512  if (!$this->getActiveId()) {
513  $this->reportable_results_available = false;
514  }
515 
516  if (!$test_obj->canShowTestResults($this)) {
517  $this->reportable_results_available = false;
518  }
519  }
520 
522  }
523 
524  public function hasSinglePassReportable(ilObjTest $testObj): bool
525  {
526  $test_passes_selector = new ilTestPassesSelector($this->db, $testObj);
527  $test_passes_selector->setActiveId($this->getActiveId());
528  $test_passes_selector->setLastFinishedPass($this->getLastFinishedPass());
529 
530  if (count($test_passes_selector->getReportablePasses()) == 1) {
531  return true;
532  }
533 
534  return false;
535  }
536 }
static get(string $a_var)
$res
Definition: ltiservices.php:66
setPasswordChecked(bool $value)
setRefId(int $ref_id)
loadTestSession(int $test_id, int $user_id=0, ?string $anonymous_id=null)
const ANONYMOUS_USER_ID
Definition: constants.php:27
persistTestStartLock(string $testStartLock)
isAccessCodeUsed(string $code)
setLastSequence(int $lastsequence)
Base Exception for all Exceptions relating to Modules/Test.
reportableResultsAvailable(ilObjTest $test_obj)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
setLastFinishedPass(int $lastFinishedPass)
setUserId(int $user_id)
hasSinglePassReportable(ilObjTest $testObj)
canShowTestResults(ilTestSession $test_session)
activeIDExists(int $user_id, int $test_id)
__construct(protected ilDBInterface $db, protected ilObjUser $user)
ilTestSession constructor
loadFromDb(int $active_id)
setAnonymousId(string $anonymous_id)
setLastStartedPass(int $lastStartedPass)
static set(string $a_var, $a_val)
Set a value.
setAccessCodeToSession(string $access_code)
setObjectiveOrientedContainerId(int $objectiveOriented)
setTestId(int $test_id)