ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
ParticipantRepository.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
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 participants.*, manscoring_done.done as scoring_finalized
313 FROM (
314 ({$this->getActiveParticipantsQuery()})
315 UNION
316 ({$this->getInvitedParticipantsQuery()})
317 ) as participants
318 LEFT JOIN manscoring_done ON manscoring_done.active_id = participants.active_id
319 ";
320 }
321
322 private function arrayToObject(array $row): Participant
323 {
324 return new Participant(
325 $row['user_fi'],
326 $row['active_id'],
327 $row['test_fi'],
328 $row['anonymous_id'],
329 $row['firstname'] ?? '',
330 $row['lastname'] ?? '',
331 $row['login'] ?? '',
332 $row['matriculation'] ?? '',
333 $row['extra_time'] ?? 0,
334 $row['tries'] ?? 0,
335 $row['ip_range_from'],
336 $row['ip_range_to'],
337 $row['invitation_date'],
338 $row['submitted'] === 1,
339 $row['last_started_pass'],
340 $row['last_finished_pass'],
341 $row['unfinished_attempts'] === 1,
342 $row['first_access'] === null ? null : new \DateTimeImmutable($row['first_access']),
343 $row['last_access'] === null ? null : new \DateTimeImmutable($row['last_access']),
344 (bool) $row['scoring_finalized']
345 );
346 }
347
351 private function getActiveParticipantsQuery(): string
352 {
353 return "
354 SELECT ta.active_id,
355 ta.user_fi,
356 ta.test_fi,
357 ta.anonymous_id,
358 ta.tries,
359 ta.submitted,
360 ta.last_finished_pass,
361 ta.last_started_pass,
362 COALESCE(ta.last_started_pass, -1) <> COALESCE(ta.last_finished_pass, -1) as unfinished_attempts,
363 ud.firstname,
364 ud.lastname,
365 ud.login,
366 ud.matriculation,
367 (SELECT MIN(started) FROM tst_times WHERE active_fi = ta.active_id) as first_access,
368 (SELECT MAX(finished) FROM tst_times WHERE active_fi = ta.active_id) as last_access,
369 tatime.additionaltime extra_time,
370 tinvited.ip_range_from,
371 tinvited.ip_range_to,
372 tinvited.tstamp as invitation_date
373 FROM tst_active ta
374 LEFT JOIN usr_data ud
375 ON ud.usr_id = ta.user_fi
376 LEFT JOIN tst_addtime tatime
377 ON tatime.user_fi = ta.user_fi
378 AND tatime.test_fi = ta.test_fi
379 LEFT JOIN tst_invited_user tinvited
380 ON tinvited.test_fi = ta.test_fi
381 AND tinvited.user_fi = ta.user_fi
382 WHERE ta.test_fi = %s
383 ";
384 }
385
389 private function getInvitedParticipantsQuery(): string
390 {
391 return "
392 SELECT ta.active_id,
393 tinvited.user_fi,
394 tinvited.test_fi,
395 ta.anonymous_id,
396 ta.tries,
397 ta.submitted,
398 ta.last_finished_pass,
399 ta.last_started_pass,
400 COALESCE(ta.last_started_pass, -1) <> COALESCE(ta.last_finished_pass, -1) as unfinished_attempts,
401 ud.firstname,
402 ud.lastname,
403 ud.login,
404 ud.matriculation,
405 NULL as first_access,
406 NULL as last_access,
407 tatime.additionaltime extra_time,
408 tinvited.ip_range_from,
409 tinvited.ip_range_to,
410 tinvited.tstamp as invitation_date
411 FROM tst_invited_user tinvited
412 LEFT JOIN usr_data ud
413 ON ud.usr_id = tinvited.user_fi
414 LEFT JOIN tst_addtime tatime
415 ON tatime.user_fi = tinvited.user_fi
416 AND tatime.test_fi = tinvited.test_fi
417 LEFT JOIN tst_active ta
418 ON tinvited.test_fi = ta.test_fi
419 AND tinvited.user_fi = ta.user_fi
420 WHERE tinvited.test_fi = %s AND ta.active_id IS NULL
421 ";
422 }
423
424 public function removeExtraTimeByUserId(int $test_id, array $user_ids): void
425 {
426 $in_user_fis = $this->database->in(
427 'user_fi',
428 $user_ids,
429 false,
431 );
432 $this->database->manipulate("DELETE FROM tst_addtime WHERE test_fi = $test_id AND $in_user_fis");
433 }
434}
Both the subject and the direction need to be specified when expressing an order.
Definition: Order.php:29
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:29
__construct(private readonly \ilDBInterface $database)
fetchParticipant(string $query, array $types, array $values)
removeExtraTimeByUserId(int $test_id, array $user_ids)
getParticipants(int $test_id, ?array $filter=null, ?Range $range=null, ?Order $order=null)
applyFilter(?array $filter, array $where, array $types, array $values)
getParticipantByActiveId(int $test_id, int $active_id)
Interface ilDBInterface.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Participant.php:21
if(!file_exists('../ilias.ini.php'))