ILIAS  trunk Revision v11.0_alpha-1846-g895b5f47236
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
ParticipantRepository.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 namespace ILIAS\Test\Participants;
22 
25 
27 {
28  public function __construct(
29  private readonly \ilDBInterface $database
30  ) {
31  }
32 
39  public function countParticipants(int $test_id, ?array $filter): int
40  {
41  $query = "
42  SELECT COUNT(participants.user_fi) as number_of_participants
43  FROM (
44  ({$this->getActiveParticipantsQuery()})
45  UNION
46  ({$this->getInvitedParticipantsQuery()})
47  ) participants
48  ";
49  [$where, $types, $values] = $this->applyFilter($filter, [], ['integer', 'integer'], [$test_id, $test_id]);
50 
51  if (!empty($where)) {
52  $where = join(' AND ', $where);
53  $query .= " WHERE $where";
54  }
55 
56  $statement = $this->database->queryF($query, $types, $values);
57  $result = $this->database->fetchAssoc($statement);
58 
59  return $result['number_of_participants'] ?? 0;
60  }
61 
65  public function getParticipants(
66  int $test_id,
67  ?array $filter = null,
68  ?Range $range = null,
69  ?Order $order = null
70  ): \Generator {
71  $query = $this->getBaseQuery();
72  [$where, $types, $values] = $this->applyFilter(
73  $filter,
74  [],
75  ['integer', 'integer'],
76  [$test_id, $test_id]
77  );
78 
79  if ($where !== []) {
80  $where = join(' AND ', $where);
81  $query .= " WHERE {$where}";
82  }
83 
84  $order_by = $this->applyOrder($order);
85 
86  if ($order_by !== '') {
87  $query .= " ORDER BY {$order_by}";
88  }
89  if ($range !== null) {
90  $query .= " LIMIT {$range->getStart()}, {$range->getLength()}";
91  }
92 
93  $statement = $this->database->queryF($query, $types, $values);
94 
95  while ($row = $this->database->fetchAssoc($statement)) {
96  yield $this->arrayToObject($row);
97  }
98  }
99 
100  public function getParticipantByActiveId(int $test_id, int $active_id): ?Participant
101  {
102  return $this->fetchParticipant(
103  "{$this->getBaseQuery()} WHERE active_id = %s",
104  ['integer', 'integer', 'integer'],
105  [$test_id, $test_id, $active_id]
106  );
107  }
108 
109  public function getParticipantByUserId(int $test_id, int $user_id): ?Participant
110  {
111  return $this->fetchParticipant(
112  "{$this->getBaseQuery()} WHERE user_fi = %s",
113  ['integer', 'integer', 'integer'],
114  [$test_id, $test_id, $user_id]
115  );
116  }
117 
118  public function updateExtraTime(Participant $participant): void
119  {
120  $this->database->manipulatef(
121  "INSERT INTO tst_addtime (user_fi, test_fi, additionaltime, tstamp) VALUES (%s, %s, %s, %s)
122  ON DUPLICATE KEY UPDATE tstamp = %s, additionaltime = %s",
123  ['integer', 'integer', 'integer','timestamp','timestamp', 'integer'],
124  [$participant->getUserId(), $participant->getTestId(), $participant->getExtraTime(), time(), time(), $participant->getExtraTime()]
125  );
126  }
127 
131  public function updateIpRange(array $participants): void
132  {
133  foreach ($participants as $participant) {
134  $this->database->replace(
135  'tst_invited_user',
136  [
137  'test_fi' => [\ilDBConstants::T_INTEGER, $participant->getTestId()],
138  'user_fi' => [\ilDBConstants::T_INTEGER, $participant->getUserId()]
139  ],
140  [
141  'ip_range_from' => [\ilDBConstants::T_TEXT, $participant->getClientIpFrom()],
142  'ip_range_to' => [\ilDBConstants::T_TEXT, $participant->getClientIpTo()],
143  'tstamp' => [\ilDBConstants::T_INTEGER, time()]
144  ]
145  );
146  }
147  }
148 
149  public function lookupTestIdByActiveId(int $active_id): int
150  {
151  $result = $this->database->queryF(
152  'SELECT test_fi FROM tst_active WHERE active_id = %s',
153  ['integer'],
154  [$active_id]
155  );
156  $test_id = -1;
157  if ($this->database->numRows($result) > 0) {
158  $row = $this->database->fetchAssoc($result);
159  $test_id = (int) $row['test_fi'];
160  }
161 
162  return $test_id;
163  }
164 
169  public function removeParticipants(array $selected_participants): void
170  {
171  $this->database->manipulate(
172  "DELETE FROM tst_invited_user WHERE test_fi = {$selected_participants[0]->getTestId()} AND "
173  . $this->database->in(
174  'user_fi',
175  array_map(
176  fn(Participant $participant): int => $participant->getUserId(),
177  $selected_participants
178  ),
179  false,
181  )
182  );
183  $this->database->manipulate(
184  "DELETE FROM tst_addtime WHERE test_fi = {$selected_participants[0]->getTestId()} AND "
185  . $this->database->in(
186  'user_fi',
187  array_map(
188  fn(Participant $participant): int => $participant->getUserId(),
189  $selected_participants
190  ),
191  false,
193  )
194  );
195  }
196 
197  public function getFirstAndLastVisitForActiveId(int $active_id): array
198  {
199  $times = $this->database->fetchAssoc(
200  $this->database->queryF(
201  'SELECT MIN(started) AS first_access, MAX(finished) AS last_access '
202  . 'FROM tst_times WHERE active_fi = %s',
203  ['integer'],
204  [$active_id]
205  )
206  );
207 
208  $start_time = null;
209  if ($times['first_access'] !== null) {
210  $start_time = new \DateTimeImmutable($times['first_access']);
211  }
212 
213  $end_time = null;
214  if ($times['last_access'] !== null) {
215  $end_time = new \DateTimeImmutable($times['last_access']);
216  }
217 
218  return ['first_access' => $start_time, 'last_access' => $end_time];
219  }
220 
225  private function fetchParticipant(string $query, array $types, array $values): ?Participant
226  {
227  $statement = $this->database->queryF($query, $types, $values);
228  $row = $this->database->fetchAssoc($statement);
229 
230  if (!$row) {
231  return null;
232  }
233 
234  return $this->arrayToObject($row);
235  }
236 
245  private function applyFilter(
246  ?array $filter,
247  array $where,
248  array $types,
249  array $values
250  ): array {
251  if ($filter === null) {
252  return [$where, $types, $values];
253  }
254 
255  if ($this->isFilterSet($filter, 'name')) {
256  $where[] = '(firstname LIKE %s OR lastname LIKE %s)';
257  $types = array_merge($types, ['string', 'string']);
258  $values = array_merge($values, ["%{$filter['name']}%", "%{$filter['name']}%"]);
259  }
260 
261  if ($this->isFilterSet($filter, 'login')) {
262  $where[] = '(login LIKE %s)';
263  $types = array_merge($types, ['string']);
264  $values = array_merge($values, ["%{$filter['login']}%"]);
265  }
266 
267  if ($this->isFilterSet($filter, 'extra_time')) {
268  if ($filter['extra_time'] === 'true') {
269  $where[] = '(extra_time > 0 AND extra_time IS NOT NULL)';
270  } else {
271  $where[] = '(extra_time = 0 OR extra_time IS NULL)';
272  }
273  }
274 
275  if ($this->isFilterSet($filter, 'ip_range')) {
276  $where[] = '(ip_range_from LIKE %s OR ip_range_to LIKE %s)';
277  $types = array_merge($types, ['string', 'string']);
278  $values = array_merge($values, ["%{$filter['ip_range']}%", "%{$filter['ip_range']}%"]);
279  }
280 
281  return [$where, $types, $values];
282  }
283 
284  private function applyOrder(?Order $order): string
285  {
286  if ($order === null) {
287  return '';
288  }
289  $order_by = [];
290  foreach ($order->get() as $subject => $direction) {
291  $order_by[] = match ($subject) {
292  'name' => "lastname {$direction}, firstname {$direction}",
293  'login' => "login {$direction}",
294  'ip_range' => "ip_range_from {$direction}, ip_range_to {$direction}",
295  'total_attempts' => "tries {$direction}",
296  'extra_time' => "extra_time {$direction}",
297  default => null
298  };
299  }
300  return trim(join(', ', array_filter($order_by)));
301  }
302 
303  private function isFilterSet(array $filter, string $key): bool
304  {
305  return isset($filter[$key]) && trim($filter[$key]) !== "";
306  }
307 
308 
309  private function getBaseQuery(): string
310  {
311  return "
312  SELECT particpants.*
313  FROM (
314  ({$this->getActiveParticipantsQuery()})
315  UNION
316  ({$this->getInvitedParticipantsQuery()})
317  ) as particpants
318  ";
319  }
320 
321  private function arrayToObject(array $row): Participant
322  {
323  return new Participant(
324  $row['user_fi'],
325  $row['active_id'],
326  $row['test_fi'],
327  $row['anonymous_id'],
328  $row['firstname'] ?? '',
329  $row['lastname'] ?? '',
330  $row['login'] ?? '',
331  $row['matriculation'] ?? '',
332  $row['extra_time'] ?? 0,
333  $row['tries'] ?? 0,
334  $row['ip_range_from'],
335  $row['ip_range_to'],
336  $row['invitation_date'],
337  $row['submitted'] === 1,
338  $row['last_started_pass'],
339  $row['last_finished_pass'],
340  $row['unfinished_attempts'] === 1,
341  $row['first_access'] === null ? null : new \DateTimeImmutable($row['first_access']),
342  $row['last_access'] === null ? null : new \DateTimeImmutable($row['last_access'])
343  );
344  }
345 
349  private function getActiveParticipantsQuery(): string
350  {
351  return "
352  SELECT ta.active_id,
353  ta.user_fi,
354  ta.test_fi,
355  ta.anonymous_id,
356  ta.tries,
357  ta.submitted,
358  ta.last_finished_pass,
359  ta.last_started_pass,
360  COALESCE(ta.last_started_pass, -1) <> COALESCE(ta.last_finished_pass, -1) as unfinished_attempts,
361  ud.firstname,
362  ud.lastname,
363  ud.login,
364  ud.matriculation,
365  (SELECT MIN(started) FROM tst_times WHERE active_fi = ta.active_id) as first_access,
366  (SELECT MAX(finished) FROM tst_times WHERE active_fi = ta.active_id) as last_access,
367  tatime.additionaltime extra_time,
368  tinvited.ip_range_from,
369  tinvited.ip_range_to,
370  tinvited.tstamp as invitation_date
371  FROM tst_active ta
372  LEFT JOIN usr_data ud
373  ON ud.usr_id = ta.user_fi
374  LEFT JOIN tst_addtime tatime
375  ON tatime.user_fi = ta.user_fi
376  AND tatime.test_fi = ta.test_fi
377  LEFT JOIN tst_invited_user tinvited
378  ON tinvited.test_fi = ta.test_fi
379  AND tinvited.user_fi = ta.user_fi
380  WHERE ta.test_fi = %s
381  ";
382  }
383 
387  private function getInvitedParticipantsQuery(): string
388  {
389  return "
390  SELECT ta.active_id,
391  tinvited.user_fi,
392  tinvited.test_fi,
393  ta.anonymous_id,
394  ta.tries,
395  ta.submitted,
396  ta.last_finished_pass,
397  ta.last_started_pass,
398  COALESCE(ta.last_started_pass, -1) <> COALESCE(ta.last_finished_pass, -1) as unfinished_attempts,
399  ud.firstname,
400  ud.lastname,
401  ud.login,
402  ud.matriculation,
403  NULL as first_access,
404  NULL as last_access,
405  tatime.additionaltime extra_time,
406  tinvited.ip_range_from,
407  tinvited.ip_range_to,
408  tinvited.tstamp as invitation_date
409  FROM tst_invited_user tinvited
410  LEFT JOIN usr_data ud
411  ON ud.usr_id = tinvited.user_fi
412  LEFT JOIN tst_addtime tatime
413  ON tatime.user_fi = tinvited.user_fi
414  AND tatime.test_fi = tinvited.test_fi
415  LEFT JOIN tst_active ta
416  ON tinvited.test_fi = ta.test_fi
417  AND tinvited.user_fi = ta.user_fi
418  WHERE tinvited.test_fi = %s AND ta.active_id IS NULL
419  ";
420  }
421 
422  public function removeExtraTimeByUserId(int $test_id, array $user_ids): void
423  {
424  $in_user_fis = $this->database->in(
425  'user_fi',
426  $user_ids,
427  false,
429  );
430  $this->database->manipulate("DELETE FROM tst_addtime WHERE test_fi = $test_id AND $in_user_fis");
431  }
432 }
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Participant.php:21
__construct(private readonly \ilDBInterface $database)
getParticipants(int $test_id, ?array $filter=null, ?Range $range=null, ?Order $order=null)
applyFilter(?array $filter, array $where, array $types, array $values)
Both the subject and the direction need to be specified when expressing an order. ...
Definition: Order.php:28
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
removeExtraTimeByUserId(int $test_id, array $user_ids)
getParticipantByActiveId(int $test_id, int $active_id)
fetchParticipant(string $query, array $types, array $values)
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:28