ILIAS  trunk Revision v11.0_alpha-1753-gb21ca8c4367
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
GeneralQuestionPropertiesRepository.php
Go to the documentation of this file.
1 <?php
2 
19 declare(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 missingResultRecordExists(int $active_id, int $pass, array $question_ids): bool
309  {
310  $in_question_ids = $this->db->in('question_fi', $question_ids, false, \ilDBConstants::T_INTEGER);
311 
312  $result = $this->db->queryF(
313  'SELECT COUNT(*) cnt'
314  . ' FROM ' . self::TEST_RESULTS_TABLE
315  . ' WHERE active_fi = %s'
316  . ' AND pass = %s'
317  . ' AND ' . $in_question_ids,
319  [$active_id, $pass]
320  );
321 
322  $row = $this->db->fetchAssoc($result);
323  return $row->cnt < count($question_ids);
324  }
325 
326  public function isInActiveTest(int $obj_id): bool
327  {
328  $result = $this->db->query(
329  'SELECT COUNT(user_fi) cnt FROM ' . self::TEST_TO_ACTIVE_USER_TABLE
330  . ' JOIN ' . self::TEST_FIXED_QUESTION_TABLE
331  . ' ON ' . self::TEST_FIXED_QUESTION_TABLE . '.test_fi = ' . self::TEST_TO_ACTIVE_USER_TABLE . '.test_fi '
332  . ' JOIN ' . self::MAIN_QUESTION_TABLE
333  . ' ON ' . self::MAIN_QUESTION_TABLE . '.question_id = ' . self::TEST_FIXED_QUESTION_TABLE . '.question_fi '
334  . ' WHERE ' . self::MAIN_QUESTION_TABLE . '.obj_fi = ' . $this->db->quote($obj_id, \ilDBConstants::T_INTEGER)
335  );
336 
337  $row = $this->db->fetchObject($result);
338  return $row->cnt > 0;
339  }
340 
341  public function questionTitleExistsInPool(int $questionpool_id, string $title): bool
342  {
343  $result = $this->db->queryF(
344  'SELECT COUNT(*) cnt FROM ' . self::MAIN_QUESTION_TABLE
345  . ' WHERE obj_fi = %s AND title = %s',
347  [$questionpool_id, $title]
348  );
349  $row = $this->db->fetchObject($result);
350  return $row->cnt > 0;
351  }
352 
354  {
355  return new GeneralQuestionProperties(
356  $this->component_factory,
357  $db_record->question_id,
358  $db_record->original_id,
359  $db_record->external_id,
360  $db_record->obj_fi,
361  $db_record->oq_obj_fi,
362  $db_record->question_type_fi,
363  $db_record->type_tag,
364  $db_record->owner,
365  $db_record->title,
366  $db_record->description,
367  $db_record->question_text,
368  $db_record->points,
369  $db_record->nr_of_tries,
370  $db_record->lifecycle,
371  $db_record->author,
372  $db_record->tstamp,
373  $db_record->created,
374  (bool) $db_record->complete,
375  $db_record->add_cont_edit_mode
376  );
377  }
378 
382  private function getForWhereClause(string $where): array
383  {
384  $query_result = $this->db->query(
385  'SELECT q.*, qt.type_tag, qt.plugin as is_plugin, qt.plugin_name, oq.obj_fi as oq_obj_fi'
386  . ' FROM ' . self::MAIN_QUESTION_TABLE . ' q'
387  . ' INNER JOIN ' . self::QUESTION_TYPES_TABLE . ' qt'
388  . ' ON q.question_type_fi = qt.question_type_id'
389  . ' LEFT JOIN ' . self::MAIN_QUESTION_TABLE . ' oq'
390  . ' ON oq.question_id = q.original_id'
391  . ' WHERE ' . $where
392  );
393 
394  $questions = [];
395  while ($db_record = $this->db->fetchObject($query_result)) {
396  if (!$this->isQuestionTypeAvailable($db_record->plugin_name)) {
397  continue;
398  }
399  $questions[$db_record->question_id] = $this
400  ->buildGeneralQuestionPropertyFromDBRecords($db_record);
401  }
402  return $questions;
403  }
404 
405  /*
406  * $param array<stdClass> $question_data
407  */
408  private function isQuestionTypeAvailable(?string $plugin_name): bool
409  {
410  if ($plugin_name === null) {
411  return true;
412  }
413 
414  $plugin_slot = !$this->component_repository->getComponentByTypeAndName(
416  'TestQuestionPool'
417  )->getPluginSlotById('qst');
418 
419  if ($plugin_slot->hasPluginName($plugin_name)) {
420  return false;
421  }
422 
423  return $plugin_slot->getPluginByName($plugin_name)->isActive();
424  }
425 }
$res
Definition: ltiservices.php:66
Readable part of repository interface to ilComponentDataDB.
usageCount(int $question_id=0)
Returns the number of place the question is in use in pools or tests.
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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)
$q
Definition: shib_logout.php:21