ILIAS  release_8 Revision v8.24
class.ilAssQuestionList.php
Go to the documentation of this file.
1<?php
2
19use ILIAS\Refinery\Factory as Refinery;
20
28{
31 private Refinery $refinery;
33
39 private $parentObjIdsFilter = array();
40
46 private $parentObjId = null;
47
53 private $parentObjType = 'qpl';
54
60 private $availableTaxonomyIds = array();
61
67 private $fieldFilters = array();
68
74 private $taxFilters = array();
75
81 private $taxParentIds = array();
82
88 private $taxParentTypes = array();
89
95 private $answerStatusActiveId = null;
96
100 private $forcedQuestionIds = array();
101
106 protected $join_obj_data = true;
107
108
112 public const QUESTION_ANSWER_STATUS_NON_ANSWERED = 'nonAnswered';
113 public const QUESTION_ANSWER_STATUS_WRONG_ANSWERED = 'wrongAnswered';
114 public const QUESTION_ANSWER_STATUS_CORRECT_ANSWERED = 'correctAnswered';
115
119 public const ANSWER_STATUS_FILTER_ALL_NON_CORRECT = 'allNonCorrect';
120 public const ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY = 'nonAnswered';
121 public const ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY = 'wrongAnswered';
122
128 private $answerStatusFilter = null;
129
130 public const QUESTION_INSTANCE_TYPE_ORIGINALS = 'QST_INSTANCE_TYPE_ORIGINALS';
131 public const QUESTION_INSTANCE_TYPE_DUPLICATES = 'QST_INSTANCE_TYPE_DUPLICATES';
132 public const QUESTION_INSTANCE_TYPE_ALL = 'QST_INSTANCE_TYPE_ALL';
134
137
138 public const QUESTION_COMPLETION_STATUS_COMPLETE = 'complete';
139 public const QUESTION_COMPLETION_STATUS_INCOMPLETE = 'incomplete';
140 public const QUESTION_COMPLETION_STATUS_BOTH = 'complete/incomplete';
142
148 protected $questions = array();
149
150 public function __construct(
153 Refinery $refinery,
155 ) {
156 $this->db = $db;
157 $this->lng = $lng;
158 $this->refinery = $refinery;
159 $this->component_repository = $component_repository;
160 }
161
162 public function getParentObjId(): ?int
163 {
164 return $this->parentObjId;
165 }
166
167 public function setParentObjId($parentObjId): void
168 {
169 $this->parentObjId = $parentObjId;
170 }
171
172 public function getParentObjectType(): string
173 {
175 }
176
177 public function setParentObjectType($parentObjType): void
178 {
179 $this->parentObjType = $parentObjType;
180 }
181
185 public function getParentObjIdsFilter(): array
186 {
188 }
189
194 {
195 $this->parentObjIdsFilter = $parentObjIdsFilter;
196 }
197
199 {
200 $this->questionInstanceTypeFilter = $questionInstanceTypeFilter;
201 }
202
204 {
206 }
207
208 public function setIncludeQuestionIdsFilter($questionIdsFilter): void
209 {
210 $this->includeQuestionIdsFilter = $questionIdsFilter;
211 }
212
214 {
216 }
217
219 {
221 }
222
224 {
225 $this->excludeQuestionIdsFilter = $excludeQuestionIdsFilter;
226 }
227
228 public function getQuestionCompletionStatusFilter(): string
229 {
231 }
232
234 {
235 $this->questionCompletionStatusFilter = $questionCompletionStatusFilter;
236 }
237
238 public function addFieldFilter($fieldName, $fieldValue): void
239 {
240 $this->fieldFilters[$fieldName] = $fieldValue;
241 }
242
243 public function addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType): void
244 {
245 $this->taxFilters[$taxId] = $taxNodes;
246 $this->taxParentIds[$taxId] = $parentObjId;
247 $this->taxParentTypes[$taxId] = $parentObjType;
248 }
249
251 {
252 $this->availableTaxonomyIds = $availableTaxonomyIds;
253 }
254
255 public function getAvailableTaxonomyIds(): array
256 {
258 }
259
261 {
262 $this->answerStatusActiveId = $answerStatusActiveId;
263 }
264
265 public function getAnswerStatusActiveId(): ?int
266 {
268 }
269
271 {
272 $this->answerStatusFilter = $answerStatusFilter;
273 }
274
275 public function getAnswerStatusFilter(): ?string
276 {
278 }
279
285 public function setJoinObjectData($a_val): void
286 {
287 $this->join_obj_data = $a_val;
288 }
289
295 public function getJoinObjectData(): bool
296 {
298 }
299
304 {
305 $this->forcedQuestionIds = $forcedQuestionIds;
306 }
307
311 public function getForcedQuestionIds(): array
312 {
314 }
315
316 private function getParentObjFilterExpression(): ?string
317 {
318 if ($this->getParentObjId()) {
319 return 'qpl_questions.obj_fi = ' . $this->db->quote($this->getParentObjId(), 'integer');
320 }
321
322 if (count($this->getParentObjIdsFilter())) {
323 return $this->db->in('qpl_questions.obj_fi', $this->getParentObjIdsFilter(), false, 'integer');
324 }
325
326 return null;
327 }
328
329 private function getFieldFilterExpressions(): array
330 {
331 $expressions = array();
332
333 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
334 switch ($fieldName) {
335 case 'title':
336 case 'description':
337 case 'author':
338 case 'lifecycle':
339
340 $expressions[] = $this->db->like('qpl_questions.' . $fieldName, 'text', "%%$fieldValue%%");
341 break;
342
343 case 'type':
344
345 $expressions[] = "qpl_qst_type.type_tag = {$this->db->quote($fieldValue, 'text')}";
346 break;
347
348 case 'question_id':
349 if ($fieldValue != "" && !is_array($fieldValue)) {
350 $fieldValue = array($fieldValue);
351 }
352 $expressions[] = $this->db->in("qpl_questions.question_id", $fieldValue, false, "integer");
353 break;
354
355 case 'parent_title':
356 if ($this->join_obj_data) {
357 $expressions[] = $this->db->like('object_data.title', 'text', "%%$fieldValue%%");
358 }
359 break;
360 }
361 }
362
363 return $expressions;
364 }
365
366 private function getTaxonomyFilterExpressions(): array
367 {
368 $expressions = array();
369
370 require_once 'Services/Taxonomy/classes/class.ilTaxonomyTree.php';
371 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
372
373 foreach ($this->taxFilters as $taxId => $taxNodes) {
374 $questionIds = array();
375
376 $forceBypass = true;
377
378 foreach ($taxNodes as $taxNode) {
379 $forceBypass = false;
380
381 $taxItemsByTaxParent = $this->getTaxItems(
382 $this->taxParentTypes[$taxId],
383 $this->taxParentIds[$taxId],
384 $taxId,
385 $taxNode
386 );
387
388 $taxItemsByParent = $this->getTaxItems(
389 $this->parentObjType,
390 $this->parentObjId,
391 $taxId,
392 $taxNode
393 );
394
395 $taxItems = array_merge($taxItemsByTaxParent, $taxItemsByParent);
396 foreach ($taxItems as $taxItem) {
397 $questionIds[$taxItem['item_id']] = $taxItem['item_id'];
398 }
399 }
400
401 if (!$forceBypass) {
402 $expressions[] = $this->db->in('question_id', $questionIds, false, 'integer');
403 }
404 }
405
406 return $expressions;
407 }
408
416 protected function getTaxItems($parentType, $parentObjId, $taxId, $taxNode): array
417 {
418 $taxTree = new ilTaxonomyTree($taxId);
419
420 $taxNodeAssignment = new ilTaxNodeAssignment(
421 $parentType,
423 'quest',
424 $taxId
425 );
426
427 $subNodes = $taxTree->getSubTreeIds($taxNode);
428 $subNodes[] = $taxNode;
429
430 return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
431 }
432
433 private function getQuestionInstanceTypeFilterExpression(): ?string
434 {
435 switch ($this->getQuestionInstanceTypeFilter()) {
437 return 'qpl_questions.original_id IS NULL';
439 return 'qpl_questions.original_id IS NOT NULL';
441 default:
442 return null;
443 }
444
445 return null;
446 }
447
448 private function getQuestionIdsFilterExpressions(): array
449 {
450 $expressions = array();
451
452 if (is_array($this->getIncludeQuestionIdsFilter())) {
453 $expressions[] = $this->db->in(
454 'qpl_questions.question_id',
456 false,
457 'integer'
458 );
459 }
460
461 if (is_array($this->getExcludeQuestionIdsFilter())) {
462 $IN = $this->db->in(
463 'qpl_questions.question_id',
465 true,
466 'integer'
467 );
468
469 if ($IN == ' 1=2 ') {
470 $IN = ' 1=1 ';
471 } // required for ILIAS < 5.0
472
473 $expressions[] = $IN;
474 }
475
476 return $expressions;
477 }
478
479 private function getParentObjectIdFilterExpression(): ?string
480 {
481 if ($this->parentObjId) {
482 return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, 'integer')}";
483 }
484
485 return null;
486 }
487
488 private function getAnswerStatusFilterExpressions(): array
489 {
490 $expressions = array();
491
492 switch ($this->getAnswerStatusFilter()) {
494
495 $expressions[] = '
496 (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
497 ';
498 break;
499
501
502 $expressions[] = 'tst_test_result.question_fi IS NULL';
503 break;
504
506
507 $expressions[] = 'tst_test_result.question_fi IS NOT NULL';
508 $expressions[] = 'tst_test_result.points < qpl_questions.points';
509 break;
510 }
511
512 return $expressions;
513 }
514
515 private function getTableJoinExpression(): string
516 {
517 $tableJoin = "
518 INNER JOIN qpl_qst_type
519 ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi
520 ";
521
522 if ($this->join_obj_data) {
523 $tableJoin .= "
524 INNER JOIN object_data
525 ON object_data.obj_id = qpl_questions.obj_fi
526 ";
527 }
528
529 if ($this->getParentObjectType() === 'tst'
530 && $this->getQuestionInstanceTypeFilter() === self::QUESTION_INSTANCE_TYPE_ALL) {
531 $tableJoin .= "
532 INNER JOIN tst_test_question tstquest
533 ON tstquest.question_fi = qpl_questions.question_id
534 ";
535 }
536
537 if ($this->getAnswerStatusActiveId()) {
538 $tableJoin .= "
539 LEFT JOIN tst_test_result
540 ON tst_test_result.question_fi = qpl_questions.question_id
541 AND tst_test_result.active_fi = {$this->db->quote($this->getAnswerStatusActiveId(), 'integer')}
542 ";
543 }
544
545 return $tableJoin;
546 }
547
548 private function getConditionalFilterExpression(): string
549 {
550 $CONDITIONS = array();
551
552 if ($this->getQuestionInstanceTypeFilterExpression() !== null) {
553 $CONDITIONS[] = $this->getQuestionInstanceTypeFilterExpression();
554 }
555
556 if ($this->getParentObjFilterExpression() !== null) {
557 $CONDITIONS[] = $this->getParentObjFilterExpression();
558 }
559
560 if ($this->getParentObjectIdFilterExpression() !== null) {
561 $CONDITIONS[] = $this->getParentObjectIdFilterExpression();
562 }
563
564 $CONDITIONS = array_merge(
565 $CONDITIONS,
570 );
571
572 $CONDITIONS = implode(' AND ', $CONDITIONS);
573
574 return strlen($CONDITIONS) ? 'AND ' . $CONDITIONS : '';
575 }
576
577 private function getSelectFieldsExpression(): string
578 {
579 $selectFields = array(
580 'qpl_questions.*',
581 'qpl_qst_type.type_tag',
582 'qpl_qst_type.plugin',
583 'qpl_qst_type.plugin_name',
584 'qpl_questions.points max_points'
585 );
586
587 if ($this->join_obj_data) {
588 $selectFields[] = 'object_data.title parent_title';
589 }
590
591 if ($this->getAnswerStatusActiveId()) {
592 $selectFields[] = 'tst_test_result.points reached_points';
593 $selectFields[] = "CASE
594 WHEN tst_test_result.points IS NULL THEN '" . self::QUESTION_ANSWER_STATUS_NON_ANSWERED . "'
595 WHEN tst_test_result.points < qpl_questions.points THEN '" . self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED . "'
596 ELSE '" . self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED . "'
597 END question_answer_status
598 ";
599 }
600
601 $selectFields = implode(",\n\t\t\t\t", $selectFields);
602
603 return "
604 SELECT {$selectFields}
605 ";
606 }
607
608 private function buildBasicQuery(): string
609 {
610 return "
611 {$this->getSelectFieldsExpression()}
612
613 FROM qpl_questions
614
615 {$this->getTableJoinExpression()}
616
617 WHERE qpl_questions.tstamp > 0
618 ";
619 }
620
621 private function buildQuery(): string
622 {
623 $query = $this->buildBasicQuery() . "
624 {$this->getConditionalFilterExpression()}
625 ";
626
627 if (count($this->getForcedQuestionIds())) {
628 $query .= "
629 UNION {$this->buildBasicQuery()}
630 AND {$this->db->in('qpl_questions.question_id', $this->getForcedQuestionIds(), false, 'integer')}
631 ";
632 }
633
634 return $query;
635 }
636
637 public function load(): void
638 {
639 $this->checkFilters();
640
641 $tags_trafo = $this->refinery->string()->stripTags();
642
643 $query = $this->buildQuery();
644
645 $res = $this->db->query($query);
646
647 while ($row = $this->db->fetchAssoc($res)) {
649
650 if (!$this->isActiveQuestionType($row)) {
651 continue;
652 }
653
654 $row['title'] = $tags_trafo->transform($row['title'] ?? '&nbsp;');
655 $row['description'] = $tags_trafo->transform($row['description'] !== '' && $row['description'] !== null ? $row['description'] : '&nbsp;');
656 $row['author'] = $tags_trafo->transform($row['author']);
657 $row['taxonomies'] = $this->loadTaxonomyAssignmentData($row['obj_fi'], $row['question_id']);
658 $row['ttype'] = $this->lng->txt($row['type_tag']);
659
660 $this->questions[ $row['question_id'] ] = $row;
661 }
662 }
663
664 private function loadTaxonomyAssignmentData($parentObjId, $questionId): array
665 {
666 $taxAssignmentData = array();
667
668 foreach ($this->getAvailableTaxonomyIds() as $taxId) {
669 require_once 'Services/Taxonomy/classes/class.ilTaxonomyTree.php';
670 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
671
672 $taxTree = new ilTaxonomyTree($taxId);
673
674 $taxAssignment = new ilTaxNodeAssignment('qpl', $parentObjId, 'quest', $taxId);
675
676 $assignments = $taxAssignment->getAssignmentsOfItem($questionId);
677
678 foreach ($assignments as $assData) {
679 if (!isset($taxAssignmentData[ $assData['tax_id'] ])) {
680 $taxAssignmentData[ $assData['tax_id'] ] = array();
681 }
682
683 $nodeData = $taxTree->getNodeData($assData['node_id']);
684
685 $assData['node_lft'] = $nodeData['lft'];
686
687 $taxAssignmentData[ $assData['tax_id'] ][ $assData['node_id'] ] = $assData;
688 }
689 }
690
691 return $taxAssignmentData;
692 }
693
694 private function isActiveQuestionType(array $questionData): bool
695 {
696 if (!isset($questionData['plugin'])) {
697 return false;
698 }
699
700 if (!$questionData['plugin']) {
701 return true;
702 }
703
704 if (!$this->component_repository->getComponentByTypeAndName(
706 'TestQuestionPool'
707 )->getPluginSlotById('qst')->hasPluginName($questionData['plugin_name'])) {
708 return false;
709 }
710
711 return $this->component_repository
712 ->getComponentByTypeAndName(
714 'TestQuestionPool'
715 )
716 ->getPluginSlotById(
717 'qst'
718 )
719 ->getPluginByName(
720 $questionData['plugin_name']
721 )->isActive();
722 }
723
724 public function getDataArrayForQuestionId($questionId)
725 {
726 return $this->questions[$questionId];
727 }
728
729 public function getQuestionDataArray(): array
730 {
731 return $this->questions;
732 }
733
734 public function isInList($questionId): bool
735 {
736 return isset($this->questions[$questionId]);
737 }
738
748 public function getTitle(string $a_comp_id, string $a_item_type, int $a_item_id): string
749 {
750 if ($a_comp_id != 'qpl' || $a_item_type != 'quest' || !$a_item_id) {
751 return '';
752 }
753
754 if (!isset($this->questions[$a_item_id])) {
755 return '';
756 }
757
758 return $this->questions[$a_item_id]['title'];
759 }
760
761 private function checkFilters(): void
762 {
763 if (strlen($this->getAnswerStatusFilter()) && !$this->getAnswerStatusActiveId()) {
764 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
765
767 'No active id given! You cannot use the answer status filter without giving an active id.'
768 );
769 }
770 }
771}
Builds data types.
Definition: Factory.php:21
isActiveQuestionType(array $questionData)
setAnswerStatusActiveId($answerStatusActiveId)
const ANSWER_STATUS_FILTER_ALL_NON_CORRECT
answer status filter value domain
setExcludeQuestionIdsFilter($excludeQuestionIdsFilter)
__construct(ilDBInterface $db, ilLanguage $lng, Refinery $refinery, ilComponentRepository $component_repository)
getDataArrayForQuestionId($questionId)
setParentObjectType($parentObjType)
getTaxItems($parentType, $parentObjId, $taxId, $taxNode)
setIncludeQuestionIdsFilter($questionIdsFilter)
setQuestionCompletionStatusFilter($questionCompletionStatusFilter)
setForcedQuestionIds($forcedQuestionIds)
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
setAvailableTaxonomyIds($availableTaxonomyIds)
getJoinObjectData()
Get if object data table should be joined.
setAnswerStatusFilter($answerStatusFilter)
ilComponentRepository $component_repository
setJoinObjectData($a_val)
Set if object data table should be joined.
getTitle(string $a_comp_id, string $a_item_type, int $a_item_id)
Get title of an assigned item.
setParentObjIdsFilter($parentObjIdsFilter)
loadTaxonomyAssignmentData($parentObjId, $questionId)
setQuestionInstanceTypeFilter($questionInstanceTypeFilter)
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
addFieldFilter($fieldName, $fieldValue)
static completeMissingPluginName($questionTypeData)
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
$query