ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilAssQuestionList.php
Go to the documentation of this file.
1<?php
2
21use ILIAS\Notes\Service as NotesService;
22use ILIAS\Refinery\Factory as Refinery;
23
31{
32 private array $parentObjIdsFilter = [];
33 private ?int $parentObjId = null;
34 private string $parentObjType = 'qpl';
35 private array $availableTaxonomyIds = [];
36 private array $fieldFilters = [];
37 private array $taxFilters = [];
39 private array $taxParentIds = [];
40 private array $taxParentTypes = [];
41 private ?int $answerStatusActiveId = null;
42 protected bool $join_obj_data = true;
43
47 public const QUESTION_ANSWER_STATUS_NON_ANSWERED = 'nonAnswered';
48 public const QUESTION_ANSWER_STATUS_WRONG_ANSWERED = 'wrongAnswered';
49 public const QUESTION_ANSWER_STATUS_CORRECT_ANSWERED = 'correctAnswered';
50
54 public const ANSWER_STATUS_FILTER_ALL_NON_CORRECT = 'allNonCorrect';
55 public const ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY = 'nonAnswered';
56 public const ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY = 'wrongAnswered';
57
58 private string $answerStatusFilter = '';
59
60 public const QUESTION_INSTANCE_TYPE_ORIGINALS = 'QST_INSTANCE_TYPE_ORIGINALS';
61 public const QUESTION_INSTANCE_TYPE_DUPLICATES = 'QST_INSTANCE_TYPE_DUPLICATES';
62 public const QUESTION_INSTANCE_TYPE_ALL = 'QST_INSTANCE_TYPE_ALL';
64
65 private array $includeQuestionIdsFilter = [];
66 private array $excludeQuestionIdsFilter = [];
67
68 public const QUESTION_COMMENTED_ONLY = '1';
69 public const QUESTION_COMMENTED_EXCLUDED = '2';
70 protected ?string $filter_comments = null;
71
72 protected array $questions = [];
73
74 private ?Order $order = null;
75 private ?Range $range = null;
76
77 public function __construct(
78 private ilDBInterface $db,
79 private ilLanguage $lng,
80 private Refinery $refinery,
81 private ilComponentRepository $component_repository,
82 private ?NotesService $notes_service = null
83 ) {
84 }
85
86 public function setOrder(?Order $order = null): void
87 {
88 $this->order = $order;
89 }
90
91 public function setRange(?Range $range = null): void
92 {
93 $this->range = $range;
94 }
95
96 public function getParentObjId(): ?int
97 {
98 return $this->parentObjId;
99 }
100
101 public function setParentObjId(?int $parentObjId): void
102 {
103 $this->parentObjId = $parentObjId;
104 }
105
106 public function setParentObjectType(string $parentObjType): void
107 {
108 $this->parentObjType = $parentObjType;
109 }
110
111 public function setParentObjIdsFilter(array $parentObjIdsFilter): void
112 {
113 $this->parentObjIdsFilter = $parentObjIdsFilter;
114 }
115
117 {
118 $this->questionInstanceTypeFilter = (string) $questionInstanceTypeFilter;
119 }
120
121 public function setIncludeQuestionIdsFilter(array $questionIdsFilter): void
122 {
123 $this->includeQuestionIdsFilter = $questionIdsFilter;
124 }
125
127 {
128 $this->excludeQuestionIdsFilter = $excludeQuestionIdsFilter;
129 }
130
131 public function addFieldFilter(string $fieldName, mixed $fieldValue): void
132 {
133 $this->fieldFilters[$fieldName] = $fieldValue;
134 }
135
136 public function addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType): void
137 {
138 $this->taxFilters[$taxId] = $taxNodes;
139 $this->taxParentIds[$taxId] = $parentObjId;
140 $this->taxParentTypes[$taxId] = $parentObjType;
141 }
142
143 public function addTaxonomyFilterNoTaxonomySet(bool $flag): void
144 {
145 $this->taxFiltersExcludeAnyObjectsWithTaxonomies = $flag;
146 }
147
149 {
150 $this->availableTaxonomyIds = $availableTaxonomyIds;
151 }
152
154 {
155 $this->answerStatusActiveId = $answerStatusActiveId;
156 }
157
158 public function setAnswerStatusFilter(string $answerStatusFilter): void
159 {
160 $this->answerStatusFilter = $answerStatusFilter;
161 }
162
166 public function setJoinObjectData(bool $a_val): void
167 {
168 $this->join_obj_data = $a_val;
169 }
170
171 private function getParentObjFilterExpression(): ?string
172 {
173 if ($this->parentObjId) {
174 return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, ilDBConstants::T_INTEGER)}";
175 }
176
177 if (!empty($this->parentObjIdsFilter)) {
178 return $this->db->in('qpl_questions.obj_fi', $this->parentObjIdsFilter, false, ilDBConstants::T_INTEGER);
179 }
180
181 return null;
182 }
183
184 private function getFieldFilterExpressions(): array
185 {
186 $expressions = [];
187
188 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
189 switch ($fieldName) {
190 case 'title':
191 case 'description':
192 case 'author':
193 case 'lifecycle':
194 $expressions[] = $this->db->like("qpl_questions.$fieldName", ilDBConstants::T_TEXT, "%%$fieldValue%%");
195 break;
196 case 'type':
197 $expressions[] = "qpl_qst_type.type_tag = {$this->db->quote($fieldValue, ilDBConstants::T_TEXT)}";
198 break;
199 case 'question_id':
200 if ($fieldValue !== '' && !is_array($fieldValue)) {
201 $fieldValue = [$fieldValue];
202 }
203 $expressions[] = $this->db->in('qpl_questions.question_id', $fieldValue, false, ilDBConstants::T_INTEGER);
204 break;
205 case 'parent_title':
206 if ($this->join_obj_data) {
207 $expressions[] = $this->db->like('object_data.title', ilDBConstants::T_TEXT, "%%$fieldValue%%");
208 }
209 break;
210 }
211 }
212
213 return $expressions;
214 }
215
216 private function handleFeedbackJoin(string $tableJoin): string
217 {
218 $feedback_join = match ($this->fieldFilters['feedback'] ?? null) {
219 'true' => 'INNER',
220 'false' => 'LEFT',
221 default => null
222 };
223
224 if (isset($feedback_join)) {
225 $SQL = "$feedback_join JOIN qpl_fb_generic ON qpl_fb_generic.question_fi = qpl_questions.question_id ";
226 $tableJoin .= !str_contains($tableJoin, $SQL) ? $SQL : '';
227 }
228
229 return $tableJoin;
230 }
231
232 private function getTaxonomyFilterExpressions(): array
233 {
234 $expressions = $this->getFilterByAssignedTaxonomyIdsExpression();
235
236 $taxonomy_title = $this->fieldFilters['taxonomy_title'] ?? '';
237 $taxonomy_node_title = $this->fieldFilters['taxonomy_node_title'] ?? '';
238
239 if ($taxonomy_title === '' && $taxonomy_node_title === '') {
240 return $expressions;
241 }
242
243 $base = 'SELECT DISTINCT item_id FROM tax_node_assignment';
244
245 $like_taxonomy_title = $taxonomy_title !== ''
246 ? "AND {$this->db->like('object_data.title', ilDBConstants::T_TEXT, "%$taxonomy_title%", false)}"
247 : '';
248 $like_taxonomy_node_title = $taxonomy_node_title !== ''
249 ? "AND {$this->db->like('tax_node.title', ilDBConstants::T_TEXT, "%$taxonomy_node_title%", false)}"
250 : '';
251
252 $inner_join_object_data = "INNER JOIN object_data ON (object_data.obj_id = tax_node_assignment.tax_id AND object_data.type = 'tax' $like_taxonomy_title)";
253 $inner_join_tax_node = "INNER JOIN tax_node ON (tax_node.tax_id = tax_node_assignment.tax_id AND tax_node.type = 'taxn' AND tax_node_assignment.node_id = tax_node.obj_id $like_taxonomy_node_title)";
254
255 $expressions[] = "qpl_questions.question_id IN ($base $inner_join_object_data $inner_join_tax_node)";
256
257 return $expressions;
258 }
259
261 {
262 if ($this->taxFiltersExcludeAnyObjectsWithTaxonomies) {
263 return ['question_id NOT IN (SELECT DISTINCT item_id FROM tax_node_assignment)'];
264 }
265
266 $expressions = [];
267 foreach ($this->taxFilters as $tax_id => $tax_nodes) {
268 $question_ids = [];
269
270 if ($tax_nodes === []) {
271 continue;
272 }
273
274 foreach ($tax_nodes as $tax_node) {
275 $tax_items_by_tax_parent = $this->getTaxItems(
276 $this->taxParentTypes[$tax_id],
277 $this->taxParentIds[$tax_id],
278 $tax_id,
279 $tax_node
280 );
281
282 $tax_items_by_parent = $this->getTaxItems(
283 $this->parentObjType,
284 $this->parentObjId,
285 $tax_id,
286 $tax_node
287 );
288
289 $tax_items = array_merge($tax_items_by_tax_parent, $tax_items_by_parent);
290 foreach ($tax_items as $tax_item) {
291 $question_ids[$tax_item['item_id']] = $tax_item['item_id'];
292 }
293 }
294
295 $expressions[] = $this->db->in('question_id', $question_ids, false, ilDBConstants::T_INTEGER);
296 }
297
298 return $expressions;
299 }
300
301 protected function getTaxItems(string $parentType, int $parentObjId, int $taxId, int $taxNode): array
302 {
303 $taxTree = new ilTaxonomyTree($taxId);
304
305 $taxNodeAssignment = new ilTaxNodeAssignment(
306 $parentType,
308 'quest',
309 $taxId
310 );
311
312 $subNodes = $taxTree->getSubTreeIds($taxNode);
313 $subNodes[] = $taxNode;
314
315 return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
316 }
317
318 private function getQuestionInstanceTypeFilterExpression(): ?string
319 {
320 return match ($this->questionInstanceTypeFilter) {
321 self::QUESTION_INSTANCE_TYPE_ORIGINALS => 'qpl_questions.original_id IS NULL',
322 self::QUESTION_INSTANCE_TYPE_DUPLICATES => 'qpl_questions.original_id IS NOT NULL',
323 default => null
324 };
325 }
326
327 private function getQuestionIdsFilterExpressions(): array
328 {
329 $expressions = [];
330
331 if (!empty($this->includeQuestionIdsFilter)) {
332 $expressions[] = $this->db->in(
333 'qpl_questions.question_id',
334 $this->includeQuestionIdsFilter,
335 false,
337 );
338 }
339
340 if (!empty($this->excludeQuestionIdsFilter)) {
341 $IN = $this->db->in(
342 'qpl_questions.question_id',
343 $this->excludeQuestionIdsFilter,
344 true,
346 );
347
348 $expressions[] = $IN === ' 1=2 ' ? ' 1=1 ' : $IN; // required for ILIAS < 5.0
349 }
350
351 return $expressions;
352 }
353
354 private function getAnswerStatusFilterExpressions(): array
355 {
356 return match ($this->answerStatusFilter) {
357 self::ANSWER_STATUS_FILTER_ALL_NON_CORRECT => ['
358 (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
359 '],
360 self::ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY => ['tst_test_result.question_fi IS NULL'],
361 self::ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY => [
362 'tst_test_result.question_fi IS NOT NULL',
363 'tst_test_result.points < qpl_questions.points'
364 ],
365 default => [],
366 };
367 }
368
369 private function getTableJoinExpression(): string
370 {
371 $tableJoin = '
372 INNER JOIN qpl_qst_type
373 ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi
374 ';
375
376 if ($this->join_obj_data) {
377 $tableJoin .= '
378 INNER JOIN object_data
379 ON object_data.obj_id = qpl_questions.obj_fi
380 ';
381 }
382
383 if (
384 $this->parentObjType === 'tst'
385 && $this->questionInstanceTypeFilter === self::QUESTION_INSTANCE_TYPE_ALL
386 ) {
387 $tableJoin .= 'INNER JOIN tst_test_question tstquest ON tstquest.question_fi = qpl_questions.question_id';
388 }
389
390 $tableJoin = $this->handleFeedbackJoin($tableJoin);
391
392 if ($this->answerStatusActiveId) {
393 $tableJoin .= "
394 LEFT JOIN tst_test_result
395 ON tst_test_result.question_fi = qpl_questions.question_id
396 AND tst_test_result.active_fi = {$this->db->quote($this->answerStatusActiveId, ilDBConstants::T_INTEGER)}
397 ";
398 }
399
400 return $tableJoin;
401 }
402
403 private function getConditionalFilterExpression(): string
404 {
405 $conditions = [];
406
407 if ($this->getQuestionInstanceTypeFilterExpression() !== null) {
408 $conditions[] = $this->getQuestionInstanceTypeFilterExpression();
409 }
410
411 if ($this->getParentObjFilterExpression() !== null) {
412 $conditions[] = $this->getParentObjFilterExpression();
413 }
414
415 $conditions = array_merge(
416 $conditions,
421 );
422
423 $conditions = implode(' AND ', $conditions);
424 return $conditions !== '' ? "AND $conditions" : '';
425 }
426
427 private function getSelectFieldsExpression(): string
428 {
429 $select_fields = [
430 'qpl_questions.*',
431 'qpl_qst_type.type_tag',
432 'qpl_qst_type.plugin',
433 'qpl_qst_type.plugin_name',
434 'qpl_questions.points max_points'
435 ];
436
437 if ($this->join_obj_data) {
438 $select_fields[] = 'object_data.title parent_title';
439 }
440
441 if ($this->answerStatusActiveId) {
442 $select_fields[] = 'tst_test_result.points reached_points';
443 $select_fields[] = "CASE
444 WHEN tst_test_result.points IS NULL THEN '" . self::QUESTION_ANSWER_STATUS_NON_ANSWERED . "'
445 WHEN tst_test_result.points < qpl_questions.points THEN '" . self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED . "'
446 ELSE '" . self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED . "'
447 END question_answer_status
448 ";
449 }
450
451 $select_fields[] = $this->generateFeedbackSubquery();
452
453 $select_fields[] = $this->generateTaxonomySubquery();
454
455 $select_fields = implode(', ', $select_fields);
456 return "SELECT DISTINCT $select_fields";
457 }
458
459 private function generateFeedbackSubquery(): string
460 {
461 $cases = [];
462 $tables = ['qpl_fb_generic', 'qpl_fb_specific'];
463
464 foreach ($tables as $table) {
465 $subquery = "SELECT 1 FROM $table WHERE $table.question_fi = qpl_questions.question_id AND $table.feedback <> ''";
466 $cases[] = "WHEN EXISTS ($subquery) THEN TRUE";
467 }
468
469 $page_object_table = 'page_object';
470 foreach ($tables as $table) {
471 $subquery = sprintf(
472 "SELECT 1 FROM $table JOIN $page_object_table ON $page_object_table.page_id = $table.feedback_id WHERE $page_object_table.parent_type IN ('%s', '%s') AND $page_object_table.is_empty <> 1 AND $table.question_fi = qpl_questions.question_id",
475 );
476 $cases[] = "WHEN EXISTS ($subquery) THEN TRUE";
477 }
478
479 $feedback_case_subquery = implode(' ', $cases);
480 return "CASE $feedback_case_subquery ELSE FALSE END AS feedback";
481 }
482
483 private function generateTaxonomySubquery(): string
484 {
485 $tax_node_assignment_table = 'tax_node_assignment';
486 $tax_subquery = "SELECT 1 FROM $tax_node_assignment_table WHERE $tax_node_assignment_table.item_id = qpl_questions.question_id AND $tax_node_assignment_table.item_type = 'quest'";
487 return "CASE WHEN EXISTS ($tax_subquery) THEN TRUE ELSE FALSE END AS taxonomies";
488 }
489
490 private function buildBasicQuery(): string
491 {
492 return "{$this->getSelectFieldsExpression()} FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0";
493 }
494
495 private function getHavingFilterExpression(): string
496 {
497 $expressions = [];
498
499 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
500 if ($fieldName === 'feedback') {
501 $fieldValue = strtoupper($fieldValue);
502 if (in_array($fieldValue, ['TRUE', 'FALSE'], true)) {
503 $expressions[] = "feedback IS $fieldValue";
504 }
505 continue;
506
507 }
508 }
509
510 $having = implode(' AND ', $expressions);
511 return $having !== '' ? "HAVING $having" : '';
512 }
513
514 private function buildOrderQueryExpression(): string
515 {
517 if ($order === null) {
518 return '';
519 }
520
521 [$order_field, $order_direction] = $order->join(
522 '',
523 static fn(string $index, string $key, string $value): array => [$key, $value]
524 );
525
526 $order_direction = strtoupper($order_direction);
527 if (!in_array($order_direction, [Order::ASC, Order::DESC], true)) {
528 $order_direction = Order::ASC;
529 }
530
531 return " ORDER BY `$order_field` $order_direction";
532 }
533
534 private function buildLimitQueryExpression(): string
535 {
537 if ($range === null) {
538 return '';
539 }
540
541 $limit = max($range->getLength(), 0);
542 $offset = max($range->getStart(), 0);
543
544 return " LIMIT $limit OFFSET $offset";
545 }
546
547 private function buildQuery(): string
548 {
549 return implode(PHP_EOL, array_filter([
550 $this->buildBasicQuery(),
555 ]));
556 }
557
558 public function load(): void
559 {
560 $this->checkFilters();
561
562 $tags_trafo = $this->refinery->encode()->htmlSpecialCharsAsEntities();
563
564 $res = $this->db->query($this->buildQuery());
565 while ($row = $this->db->fetchAssoc($res)) {
567
568 if (!$this->isActiveQuestionType($row)) {
569 continue;
570 }
571
572 $row['title'] = $tags_trafo->transform($row['title'] ?? '&nbsp;');
573 $row['description'] = $tags_trafo->transform($row['description'] ?? '');
574 $row['author'] = $tags_trafo->transform($row['author']);
575 $row['taxonomies'] = $this->loadTaxonomyAssignmentData($row['obj_fi'], $row['question_id']);
576 $row['ttype'] = $this->lng->txt($row['type_tag']);
577 $row['feedback'] = $row['feedback'] === 1;
578 $row['comments'] = $this->getNumberOfCommentsForQuestion($row['question_id']);
579
580 if (
581 $this->filter_comments === self::QUESTION_COMMENTED_ONLY && $row['comments'] === 0
582 || $this->filter_comments === self::QUESTION_COMMENTED_EXCLUDED && $row['comments'] > 0
583 ) {
584 continue;
585 }
586
587 $this->questions[$row['question_id']] = $row;
588 }
589 }
590
591 public function getTotalRowCount(?array $filter_data, ?array $additional_parameters): ?int
592 {
593 $this->checkFilters();
594
595 $count = 'COUNT(*)';
596 $query = "SELECT $count FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0 {$this->getConditionalFilterExpression()}";
597
598 return (int) ($this->db->query($query)->fetch()[$count] ?? 0);
599 }
600
601 protected function getNumberOfCommentsForQuestion(int $question_id): int
602 {
603 if ($this->notes_service === null) {
604 return 0;
605 }
606 $notes_context = $this->notes_service->data()->context(
607 $this->getParentObjId(),
608 $question_id,
609 'quest'
610 );
611 return $this->notes_service->domain()->getNrOfCommentsForContext($notes_context);
612 }
613
614 public function setCommentFilter(?int $commented = null): void
615 {
616 $this->filter_comments = $commented;
617 }
618
620 int $parent_obj_id,
621 int $question_id
622 ): array {
623 $tax_assignment_data = [];
624 foreach ($this->availableTaxonomyIds as $tax_id) {
625 $tax_tree = new ilTaxonomyTree($tax_id);
626
627 $tax_assignment = new ilTaxNodeAssignment('qpl', $parent_obj_id, 'quest', $tax_id);
628 $assignments = $tax_assignment->getAssignmentsOfItem($question_id);
629
630 foreach ($assignments as $ass_data) {
631 if (!isset($tax_assignment_data[$ass_data['tax_id']])) {
632 $tax_assignment_data[$ass_data['tax_id']] = [];
633 }
634
635 $ass_data['node_lft'] = $tax_tree->getNodeData($ass_data['node_id']);
636
637 $tax_assignment_data[$ass_data['tax_id']][$ass_data['node_id']] = $ass_data;
638 }
639 }
640
641 return $tax_assignment_data;
642 }
643
644 private function isActiveQuestionType(array $questionData): bool
645 {
646 if (!isset($questionData['plugin'])) {
647 return false;
648 }
649
650 if (!$questionData['plugin']) {
651 return true;
652 }
653
654 if (
655 !isset($questionData['plugin_name'])
656 || !$this->component_repository->getComponentByTypeAndName(
658 'TestQuestionPool'
659 )->getPluginSlotById('qst')->hasPluginName($questionData['plugin_name'])
660 ) {
661 return false;
662 }
663
664 return $this->component_repository
665 ->getComponentByTypeAndName(ilComponentInfo::TYPE_COMPONENT, 'TestQuestionPool')
666 ->getPluginSlotById('qst')
667 ->getPluginByName($questionData['plugin_name'])
668 ->isActive();
669 }
670
671 public function getDataArrayForQuestionId(int $questionId)
672 {
673 return $this->questions[$questionId];
674 }
675
676 public function getQuestionDataArray(): array
677 {
678 return $this->questions;
679 }
680
681 public function isInList(int $questionId): bool
682 {
683 return isset($this->questions[$questionId]);
684 }
685
695 public function getTitle(string $a_comp_id, string $a_item_type, int $a_item_id): string
696 {
697 if ($a_comp_id !== 'qpl' || $a_item_type !== 'quest' || !$a_item_id) {
698 return '';
699 }
700
701 return $this->questions[$a_item_id]['title'] ?? '';
702 }
703
704 private function checkFilters(): void
705 {
706 if ($this->answerStatusFilter !== '' && !$this->answerStatusActiveId) {
708 'No active id given! You cannot use the answer status filter without giving an active id.'
709 );
710 }
711 }
712}
Builds data types.
Definition: Factory.php:36
Both the subject and the direction need to be specified when expressing an order.
Definition: Order.php:29
join($init, callable $fn)
Definition: Order.php:75
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:29
const PAGE_OBJECT_TYPE_GENERIC_FEEDBACK
type for generic feedback page objects
const PAGE_OBJECT_TYPE_SPECIFIC_FEEDBACK
type for specific feedback page objects
setQuestionInstanceTypeFilter(?string $questionInstanceTypeFilter)
isActiveQuestionType(array $questionData)
getTotalRowCount(?array $filter_data, ?array $additional_parameters)
setParentObjId(?int $parentObjId)
const ANSWER_STATUS_FILTER_ALL_NON_CORRECT
answer status filter value domain
setCommentFilter(?int $commented=null)
handleFeedbackJoin(string $tableJoin)
setParentObjIdsFilter(array $parentObjIdsFilter)
getDataArrayForQuestionId(int $questionId)
getNumberOfCommentsForQuestion(int $question_id)
setOrder(?Order $order=null)
loadTaxonomyAssignmentData(int $parent_obj_id, int $question_id)
setIncludeQuestionIdsFilter(array $questionIdsFilter)
setAvailableTaxonomyIds(array $availableTaxonomyIds)
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
setJoinObjectData(bool $a_val)
Set if object data table should be joined.
__construct(private ilDBInterface $db, private ilLanguage $lng, private Refinery $refinery, private ilComponentRepository $component_repository, private ?NotesService $notes_service=null)
setExcludeQuestionIdsFilter(array $excludeQuestionIdsFilter)
addFieldFilter(string $fieldName, mixed $fieldValue)
getTaxItems(string $parentType, int $parentObjId, int $taxId, int $taxNode)
setAnswerStatusActiveId(?int $answerStatusActiveId)
getTitle(string $a_comp_id, string $a_item_type, int $a_item_id)
Get title of an assigned item.
setRange(?Range $range=null)
setParentObjectType(string $parentObjType)
setAnswerStatusFilter(string $answerStatusFilter)
addTaxonomyFilterNoTaxonomySet(bool $flag)
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
static completeMissingPluginName(array $question_type_data)
language handling
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Readable part of repository interface to ilComponentDataDB.
Interface ilDBInterface.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$res
Definition: ltiservices.php:69
global $lng
Definition: privfeed.php:31