ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
GeneralQuestionPropertiesRepository.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
24{
25 private const MAIN_QUESTION_TABLE = 'qpl_questions';
26 private const QUESTION_TYPES_TABLE = 'qpl_qst_type';
27 private const TEST_FIXED_QUESTION_TABLE = 'tst_test_question';
28 private const TEST_RANDOM_QUESTION_TABLE = 'tst_test_rnd_qst';
29 private const TEST_RESULTS_TABLE = 'tst_test_result';
30 private const TEST_TO_ACTIVE_USER_TABLE = 'tst_active';
31 private const DATA_TABLE = 'object_data';
32
33 public function __construct(
34 private \ilDBInterface $db,
35 private \ilComponentFactory $component_factory,
36 private \ilComponentRepository $component_repository
37 ) {
38 }
39
40 public function getForQuestionId(int $question_id): ?GeneralQuestionProperties
41 {
42 if ($question_id < 1) {
43 return new GeneralQuestionProperties($this->component_factory, $question_id);
44 }
45
46 $question_data = $this->getForWhereClause('q.question_id=' . $question_id);
47 return $question_data[$question_id] ?? null;
48 }
49
53 public function getForParentObjectId(int $obj_id): array
54 {
55 return $this->getForWhereClause('q.obj_fi=' . $obj_id);
56 }
57
63 public function getForQuestionIds(array $question_ids): array
64 {
65 return $this->getForWhereClause(
66 $this->db->in(
67 'q.question_id',
68 $question_ids,
69 false,
71 )
72 );
73 }
74
75 public function getFractionOfReachedToReachablePointsTotal(int $question_id): float
76 {
77 $questions_result = $this->db->queryF(
78 'SELECT question_id, points FROM ' . self::MAIN_QUESTION_TABLE . ' WHERE original_id = %s OR question_id = %s',
80 [$question_id, $question_id]
81 );
82 if ($this->db->numRows($questions_result) === 0) {
83 return 0.0;
84 }
85
86 $found_questions = [];
87 while ($found_questions_row = $this->db->fetchObject($questions_result)) {
88 $found_questions[$found_questions_row->question_id] = $found_questions_row->points;
89 }
90
91 $points_result = $this->db->query(
92 'SELECT question_fi, points FROM ' . self::TEST_RESULTS_TABLE
93 . ' WHERE ' . $this->db->in('question_fi', array_keys($found_questions), false, \ilDBConstants::T_INTEGER)
94 );
95
96 $answers = [];
97 while ($points_row = $this->db->fetchObject($points_result)) {
98 $answers[] = [
99 'reached' => $points_row->points,
100 'reachable' => $found_questions[$points_row->question_fi]
101 ];
102 }
103
104 $reachable = 0.0;
105 $reached = 0.0;
106 foreach ($answers as $points) {
107 $reachable += $points['reachable'];
108 $reached += $points['reached'];
109 }
110 if ($reachable > 0) {
111 return $reached / $reachable;
112 }
113 return 0;
114 }
115
122 public function areQuestionsAnsweredByUser(int $user_id, array $question_ids): bool
123 {
124 $result = $this->db->queryF(
125 'SELECT COUNT(DISTINCT question_fi) cnt FROM ' . self::TEST_RESULTS_TABLE . ' JOIN tst_active'
126 . ' ON (active_id = active_fi)'
127 . ' WHERE ' . $this->db->in('question_fi', $question_ids, false, \ilDBConstants::T_INTEGER)
128 . ' AND user_fi = %s',
130 [$user_id]
131 );
132
133 $row = $this->db->fetchObject($result);
134 return $row->cnt === count($question_ids);
135 }
136
137 public function lookupResultRecordExist(int $active_id, int $question_id, int $pass): bool
138 {
139 $result = $this->db->queryF(
140 'SELECT COUNT(*) cnt '
141 . ' FROM ' . self::TEST_RESULTS_TABLE
142 . ' WHERE active_fi = %s'
143 . ' AND question_fi = %s'
144 . ' AND pass = %s',
146 [$active_id, $question_id, $pass]
147 );
148
149 $row = $this->db->fetchObject($result);
150 return $row->cnt > 0;
151 }
152
153 public function isInUse(int $question_id = 0): bool
154 {
155 return $this->usageCount($question_id) > 0;
156 }
157
161 public function usageCount(int $question_id = 0): int
162 {
163 $result_tests_fixed = $this->db->queryF(
164 'SELECT COUNT(' . self::MAIN_QUESTION_TABLE . '.question_id) question_count'
165 . ' FROM ' . self::MAIN_QUESTION_TABLE . ', ' . self::TEST_FIXED_QUESTION_TABLE
166 . ' WHERE ' . self::MAIN_QUESTION_TABLE . '.question_id = ' . self::TEST_FIXED_QUESTION_TABLE . '.question_fi'
167 . ' AND ' . self::MAIN_QUESTION_TABLE . '.original_id = %s',
169 [$question_id]
170 );
171 $row_tests_fixed = $this->db->fetchObject($result_tests_fixed);
172 $count = $row_tests_fixed->question_count;
173
174 $result_tests_random = $this->db->queryF(
175 'SELECT COUNT(' . self::TEST_TO_ACTIVE_USER_TABLE . '.test_fi) question_count'
176 . ' FROM ' . self::MAIN_QUESTION_TABLE
177 . ' INNER JOIN ' . self::TEST_RANDOM_QUESTION_TABLE
178 . ' ON ' . self::TEST_RANDOM_QUESTION_TABLE . '.question_fi = ' . self::MAIN_QUESTION_TABLE . '.question_id'
179 . ' INNER JOIN ' . self::TEST_TO_ACTIVE_USER_TABLE
180 . ' ON ' . self::TEST_TO_ACTIVE_USER_TABLE . '.active_id = ' . self::TEST_RANDOM_QUESTION_TABLE . '.active_fi'
181 . ' WHERE ' . self::MAIN_QUESTION_TABLE . '.original_id = %s'
182 . ' GROUP BY tst_active.test_fi',
184 [$question_id]
185 );
186 $row_tests_random = $this->db->fetchObject($result_tests_random);
187 if ($row_tests_random !== null) {
188 $count += $row_tests_random->question_count;
189 }
190
191 return $count;
192 }
193
197 public function searchQuestionIdsByTitle(string $title): array
198 {
199 if ($title === '') {
200 return [];
201 }
202
203 $result = $this->db->query(
204 'SELECT question_id FROM ' . self::MAIN_QUESTION_TABLE . ' WHERE '
205 . $this->db->like('title', \ilDBConstants::T_TEXT, "%{$title}%"),
206 );
207
208 return array_map(
209 static fn(\stdClass $q): int => $q->question_id,
210 $this->db->fetchAll($result, \ilDBConstants::FETCHMODE_OBJECT)
211 );
212 }
213
214 public function questionExists(int $question_id): bool
215 {
216 if ($question_id < 1) {
217 return false;
218 }
219
220 $result = $this->db->queryF(
221 'SELECT COUNT(question_id) cnt FROM ' . self::MAIN_QUESTION_TABLE . ' WHERE question_id = %s',
223 [$question_id]
224 );
225
226 $row = $this->db->fetchObject($result);
227 return $row->cnt === 1;
228 }
229
230 public function questionExistsInPool(int $question_id): bool
231 {
232 if ($question_id < 1) {
233 return false;
234 }
235
236 $result = $this->db->queryF(
237 'SELECT COUNT(question_id) cnt FROM ' . self::MAIN_QUESTION_TABLE
238 . ' INNER JOIN ' . self::DATA_TABLE . ' ON obj_fi = obj_id WHERE question_id = %s AND type = "qpl"',
240 [$question_id]
241 );
242
243 $row = $this->db->fetchObject($result);
244 return $row->cnt === 1;
245 }
246
247 public function isUsedInRandomTest(int $question_id): bool
248 {
249 $result = $this->db->queryF(
250 'SELECT COUNT(test_random_question_id) cnt'
251 . ' FROM ' . self::TEST_RANDOM_QUESTION_TABLE
252 . ' WHERE question_fi = %s',
254 [$question_id]
255 );
256
257 $row = $this->db->fetchObject($result);
258 return $row->cnt > 0;
259 }
260
261 public function originalQuestionExists(int $question_id): bool
262 {
263 $res = $this->db->queryF(
264 'SELECT COUNT(dupl.question_id) cnt'
265 . ' FROM ' . self::MAIN_QUESTION_TABLE . ' dupl'
266 . ' INNER JOIN ' . self::MAIN_QUESTION_TABLE . ' orig'
267 . ' ON orig.question_id = dupl.original_id'
268 . ' WHERE dupl.question_id = %s',
270 [$question_id]
271 );
272 $row = $this->db->fetchObject($res);
273
274 return $row->cnt > 0;
275 }
276
278 int $active_id,
279 int $pass,
280 array $question_ids
281 ): array {
282 $in_question_ids = $this->db->in('question_fi', $question_ids, false, \ilDBConstants::T_INTEGER);
283
284 $result = $this->db->queryF(
285 'SELECT question_fi'
286 . ' FROM ' . self::TEST_RESULTS_TABLE
287 . ' WHERE active_fi = %s'
288 . ' AND pass = %s'
289 . ' AND ' . $in_question_ids,
291 [$active_id, $pass]
292 );
293
294 $questions_having_result_record = [];
295
296 while ($row = $this->db->fetchObject($result)) {
297 $questions_having_result_record[] = $row->question_fi;
298 }
299
300 $questions_missing_result_record = array_diff(
301 $question_ids,
302 $questions_having_result_record
303 );
304
305 return $questions_missing_result_record;
306 }
307
308 public function isInActiveTest(int $obj_id): bool
309 {
310 $result = $this->db->query(
311 'SELECT COUNT(user_fi) cnt FROM ' . self::TEST_TO_ACTIVE_USER_TABLE
312 . ' JOIN ' . self::TEST_FIXED_QUESTION_TABLE
313 . ' ON ' . self::TEST_FIXED_QUESTION_TABLE . '.test_fi = ' . self::TEST_TO_ACTIVE_USER_TABLE . '.test_fi '
314 . ' JOIN ' . self::MAIN_QUESTION_TABLE
315 . ' ON ' . self::MAIN_QUESTION_TABLE . '.question_id = ' . self::TEST_FIXED_QUESTION_TABLE . '.question_fi '
316 . ' WHERE ' . self::MAIN_QUESTION_TABLE . '.obj_fi = ' . $this->db->quote($obj_id, \ilDBConstants::T_INTEGER)
317 );
318
319 $row = $this->db->fetchObject($result);
320 return $row->cnt > 0;
321 }
322
324 {
325 return new GeneralQuestionProperties(
326 $this->component_factory,
327 $db_record->question_id,
328 $db_record->original_id,
329 $db_record->external_id,
330 $db_record->obj_fi,
331 $db_record->oq_obj_fi,
332 $db_record->question_type_fi,
333 $db_record->type_tag,
334 $db_record->owner,
335 $db_record->title,
336 $db_record->description,
337 $db_record->question_text,
338 $db_record->points,
339 $db_record->nr_of_tries,
340 $db_record->lifecycle,
341 $db_record->author,
342 $db_record->tstamp,
343 $db_record->created,
344 (bool) $db_record->complete,
345 $db_record->add_cont_edit_mode
346 );
347 }
348
352 private function getForWhereClause(string $where): array
353 {
354 $query_result = $this->db->query(
355 'SELECT q.*, qt.type_tag, qt.plugin as is_plugin, qt.plugin_name, oq.obj_fi as oq_obj_fi'
356 . ' FROM ' . self::MAIN_QUESTION_TABLE . ' q'
357 . ' INNER JOIN ' . self::QUESTION_TYPES_TABLE . ' qt'
358 . ' ON q.question_type_fi = qt.question_type_id'
359 . ' LEFT JOIN ' . self::MAIN_QUESTION_TABLE . ' oq'
360 . ' ON oq.question_id = q.original_id'
361 . ' WHERE ' . $where
362 );
363
364 $questions = [];
365 while ($db_record = $this->db->fetchObject($query_result)) {
366 if (!$this->isQuestionTypeAvailable($db_record->plugin_name)) {
367 continue;
368 }
369 $questions[$db_record->question_id] = $this
370 ->buildGeneralQuestionPropertyFromDBRecords($db_record);
371 }
372 return $questions;
373 }
374
375 /*
376 * $param array<stdClass> $question_data
377 */
378 private function isQuestionTypeAvailable(?string $plugin_name): bool
379 {
380 if ($plugin_name === null) {
381 return true;
382 }
383
384 $plugin_slot = $this->component_repository->getComponentByTypeAndName(
386 'TestQuestionPool'
387 )->getPluginSlotById('qst');
388
389 if (!$plugin_slot->hasPluginName($plugin_name)) {
390 return false;
391 }
392
393 return $plugin_slot->getPluginByName($plugin_name)->isActive();
394 }
395}
areQuestionsAnsweredByUser(int $user_id, array $question_ids)
Checks if an array of question ids is answered by a user or not.
__construct(private \ilDBInterface $db, private \ilComponentFactory $component_factory, private \ilComponentRepository $component_repository)
usageCount(int $question_id=0)
Returns the number of place the question is in use in pools or tests.
Class ilDBConstants.
Readable part of repository interface to ilComponentDataDB.
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$q
Definition: shib_logout.php:23