ILIAS  release_7 Revision v7.30-3-g800a261c036
class.ilAssQuestionList.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once 'Services/Taxonomy/interfaces/interface.ilTaxAssignedItemInfo.php';
5require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionType.php';
6
7use ILIAS\Refinery\Factory as Refinery;
8
19{
25 protected $db = null;
26
32 private $lng = null;
33
39 private $pluginAdmin = null;
40
44 protected $refinery;
45
51 private $parentObjIdsFilter = array();
52
58 private $parentObjId = null;
59
65 private $parentObjType = 'qpl';
66
72 private $availableTaxonomyIds = array();
73
79 private $fieldFilters = array();
80
86 private $taxFilters = array();
87
93 private $taxParentIds = array();
94
100 private $taxParentTypes = array();
101
107 private $answerStatusActiveId = null;
108
112 private $forcedQuestionIds = array();
113
118 protected $join_obj_data = true;
119
120
127
134
140 private $answerStatusFilter = null;
141
142 public const QUESTION_INSTANCE_TYPE_ORIGINALS = 'QST_INSTANCE_TYPE_ORIGINALS';
143 public const QUESTION_INSTANCE_TYPE_DUPLICATES = 'QST_INSTANCE_TYPE_DUPLICATES';
144 public const QUESTION_INSTANCE_TYPE_ALL = 'QST_INSTANCE_TYPE_ALL';
145
147
150
153 const QUESTION_COMPLETION_STATUS_BOTH = 'complete/incomplete';
155
161 protected $questions = array();
162
163 public function __construct(
166 ILIAS\Refinery\Factory $refinery,
168 ) {
169 $this->db = $db;
170 $this->lng = $lng;
171 $this->refinery = $refinery;
172 $this->pluginAdmin = $pluginAdmin;
173 }
174
175 public function getParentObjId()
176 {
177 return $this->parentObjId;
178 }
179
181 {
182 $this->parentObjId = $parentObjId;
183 }
184
185 public function getParentObjectType()
186 {
188 }
189
191 {
192 $this->parentObjType = $parentObjType;
193 }
194
198 public function getParentObjIdsFilter()
199 {
201 }
202
207 {
208 $this->parentObjIdsFilter = $parentObjIdsFilter;
209 }
210
212 {
213 $this->questionInstanceTypeFilter = $questionInstanceTypeFilter;
214 }
215
217 {
219 }
220
221 public function setIncludeQuestionIdsFilter($questionIdsFilter)
222 {
223 $this->includeQuestionIdsFilter = $questionIdsFilter;
224 }
225
227 {
229 }
230
232 {
234 }
235
237 {
238 $this->excludeQuestionIdsFilter = $excludeQuestionIdsFilter;
239 }
240
242 {
244 }
245
247 {
248 $this->questionCompletionStatusFilter = $questionCompletionStatusFilter;
249 }
250
251 public function addFieldFilter($fieldName, $fieldValue)
252 {
253 $this->fieldFilters[$fieldName] = $fieldValue;
254 }
255
256 public function addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
257 {
258 $this->taxFilters[$taxId] = $taxNodes;
259 $this->taxParentIds[$taxId] = $parentObjId;
260 $this->taxParentTypes[$taxId] = $parentObjType;
261 }
262
264 {
265 $this->availableTaxonomyIds = $availableTaxonomyIds;
266 }
267
268 public function getAvailableTaxonomyIds()
269 {
271 }
272
274 {
275 $this->answerStatusActiveId = $answerStatusActiveId;
276 }
277
278 public function getAnswerStatusActiveId()
279 {
281 }
282
284 {
285 $this->answerStatusFilter = $answerStatusFilter;
286 }
287
288 public function getAnswerStatusFilter()
289 {
291 }
292
298 public function setJoinObjectData($a_val)
299 {
300 $this->join_obj_data = $a_val;
301 }
302
308 public function getJoinObjectData()
309 {
311 }
312
317 {
318 $this->forcedQuestionIds = $forcedQuestionIds;
319 }
320
324 public function getForcedQuestionIds()
325 {
327 }
328
330 {
331 if ($this->getParentObjId()) {
332 return 'qpl_questions.obj_fi = ' . $this->db->quote($this->getParentObjId(), 'integer');
333 }
334
335 if (count($this->getParentObjIdsFilter())) {
336 return $this->db->in('qpl_questions.obj_fi', $this->getParentObjIdsFilter(), false, 'integer');
337 }
338
339 return null;
340 }
341
342 private function getFieldFilterExpressions()
343 {
344 $expressions = array();
345
346 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
347 switch ($fieldName) {
348 case 'title':
349 case 'description':
350 case 'author':
351 case 'lifecycle':
352
353 $expressions[] = $this->db->like('qpl_questions.' . $fieldName, 'text', "%%$fieldValue%%");
354 break;
355
356 case 'type':
357
358 $expressions[] = "qpl_qst_type.type_tag = {$this->db->quote($fieldValue, 'text')}";
359 break;
360
361 case 'question_id':
362 if ($fieldValue != "" && !is_array($fieldValue)) {
363 $fieldValue = array($fieldValue);
364 }
365 $expressions[] = $this->db->in("qpl_questions.question_id", $fieldValue, false, "integer");
366 break;
367
368 case 'parent_title':
369 if ($this->join_obj_data) {
370 $expressions[] = $this->db->like('object_data.title', 'text', "%%$fieldValue%%");
371 }
372 break;
373 }
374 }
375
376 return $expressions;
377 }
378
380 {
381 $expressions = array();
382
383 require_once 'Services/Taxonomy/classes/class.ilTaxonomyTree.php';
384 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
385
386 foreach ($this->taxFilters as $taxId => $taxNodes) {
387 $questionIds = array();
388
389 $forceBypass = true;
390
391 foreach ($taxNodes as $taxNode) {
392 $forceBypass = false;
393
394 $taxItemsByTaxParent = $this->getTaxItems(
395 $this->taxParentTypes[$taxId],
396 $this->taxParentIds[$taxId],
397 $taxId,
398 $taxNode
399 );
400
401 $taxItemsByParent = $this->getTaxItems(
402 $this->parentObjType,
403 $this->parentObjId,
404 $taxId,
405 $taxNode
406 );
407
408 $taxItems = array_merge($taxItemsByTaxParent, $taxItemsByParent);
409 foreach ($taxItems as $taxItem) {
410 $questionIds[$taxItem['item_id']] = $taxItem['item_id'];
411 }
412 }
413
414 if (!$forceBypass) {
415 $expressions[] = $this->db->in('question_id', $questionIds, false, 'integer');
416 }
417 }
418
419 return $expressions;
420 }
421
429 protected function getTaxItems($parentType, $parentObjId, $taxId, $taxNode)
430 {
431 $taxTree = new ilTaxonomyTree($taxId);
432
433 $taxNodeAssignment = new ilTaxNodeAssignment(
434 $parentType,
436 'quest',
437 $taxId
438 );
439
440 $subNodes = $taxTree->getSubTreeIds($taxNode);
441 $subNodes[] = $taxNode;
442
443 return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
444 }
445
447 {
448 switch ($this->getQuestionInstanceTypeFilter()) {
450 return 'qpl_questions.original_id IS NULL';
452 return 'qpl_questions.original_id IS NOT NULL';
454 default:
455 return null;
456 }
457
458 return null;
459 }
460
462 {
463 $expressions = array();
464
465 if (is_array($this->getIncludeQuestionIdsFilter())) {
466 $expressions[] = $this->db->in(
467 'qpl_questions.question_id',
469 false,
470 'integer'
471 );
472 }
473
474 if (is_array($this->getExcludeQuestionIdsFilter())) {
475 $IN = $this->db->in(
476 'qpl_questions.question_id',
478 true,
479 'integer'
480 );
481
482 if ($IN == ' 1=2 ') {
483 $IN = ' 1=1 ';
484 } // required for ILIAS < 5.0
485
486 $expressions[] = $IN;
487 }
488
489 return $expressions;
490 }
491
493 {
494 if ($this->parentObjId) {
495 return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, 'integer')}";
496 }
497
498 return null;
499 }
500
502 {
503 $expressions = array();
504
505 switch ($this->getAnswerStatusFilter()) {
507
508 $expressions[] = '
509 (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
510 ';
511 break;
512
514
515 $expressions[] = 'tst_test_result.question_fi IS NULL';
516 break;
517
519
520 $expressions[] = 'tst_test_result.question_fi IS NOT NULL';
521 $expressions[] = 'tst_test_result.points < qpl_questions.points';
522 break;
523 }
524
525 return $expressions;
526 }
527
528 private function getTableJoinExpression()
529 {
530 $tableJoin = "
531 INNER JOIN qpl_qst_type
532 ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi
533 ";
534
535 if ($this->join_obj_data) {
536 $tableJoin .= "
537 INNER JOIN object_data
538 ON object_data.obj_id = qpl_questions.obj_fi
539 ";
540 }
541
542 if ($this->getParentObjectType() === 'tst'
543 && $this->getQuestionInstanceTypeFilter() === self::QUESTION_INSTANCE_TYPE_ALL) {
544 $tableJoin .= "
545 INNER JOIN tst_test_question tstquest
546 ON tstquest.question_fi = qpl_questions.question_id
547 ";
548 }
549
550 if ($this->getAnswerStatusActiveId()) {
551 $tableJoin .= "
552 LEFT JOIN tst_test_result
553 ON tst_test_result.question_fi = qpl_questions.question_id
554 AND tst_test_result.active_fi = {$this->db->quote($this->getAnswerStatusActiveId(), 'integer')}
555 ";
556 }
557
558 return $tableJoin;
559 }
560
562 {
563 $CONDITIONS = array();
564
565 if ($this->getQuestionInstanceTypeFilterExpression() !== null) {
566 $CONDITIONS[] = $this->getQuestionInstanceTypeFilterExpression();
567 }
568
569 if ($this->getParentObjFilterExpression() !== null) {
570 $CONDITIONS[] = $this->getParentObjFilterExpression();
571 }
572
573 if ($this->getParentObjectIdFilterExpression() !== null) {
574 $CONDITIONS[] = $this->getParentObjectIdFilterExpression();
575 }
576
577 $CONDITIONS = array_merge(
578 $CONDITIONS,
583 );
584
585 $CONDITIONS = implode(' AND ', $CONDITIONS);
586
587 return strlen($CONDITIONS) ? 'AND ' . $CONDITIONS : '';
588 }
589
590 private function getSelectFieldsExpression()
591 {
592 $selectFields = array(
593 'qpl_questions.*',
594 'qpl_qst_type.type_tag',
595 'qpl_qst_type.plugin',
596 'qpl_qst_type.plugin_name',
597 'qpl_questions.points max_points'
598 );
599
600 if ($this->join_obj_data) {
601 $selectFields[] = 'object_data.title parent_title';
602 }
603
604 if ($this->getAnswerStatusActiveId()) {
605 $selectFields[] = 'tst_test_result.points reached_points';
606 $selectFields[] = "CASE
607 WHEN tst_test_result.points IS NULL THEN '" . self::QUESTION_ANSWER_STATUS_NON_ANSWERED . "'
608 WHEN tst_test_result.points < qpl_questions.points THEN '" . self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED . "'
609 ELSE '" . self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED . "'
610 END question_answer_status
611 ";
612 }
613
614 $selectFields = implode(",\n\t\t\t\t", $selectFields);
615
616 return "
617 SELECT {$selectFields}
618 ";
619 }
620
621 private function buildBasicQuery()
622 {
623 return "
624 {$this->getSelectFieldsExpression()}
625
626 FROM qpl_questions
627
628 {$this->getTableJoinExpression()}
629
630 WHERE qpl_questions.tstamp > 0
631 ";
632 }
633
634 private function buildQuery()
635 {
636 $query = $this->buildBasicQuery() . "
637 {$this->getConditionalFilterExpression()}
638 ";
639
640 if (count($this->getForcedQuestionIds())) {
641 $query .= "
642 UNION {$this->buildBasicQuery()}
643 AND {$this->db->in('qpl_questions.question_id', $this->getForcedQuestionIds(), false, 'integer')}
644 ";
645 }
646
647 return $query;
648 }
649
650 public function load()
651 {
652 $this->checkFilters();
653
654 $tags_trafo = $this->refinery->string()->stripTags();
655
656 $query = $this->buildQuery();
657
658 $res = $this->db->query($query);
659
660 while ($row = $this->db->fetchAssoc($res)) {
662
663 if (!$this->isActiveQuestionType($row)) {
664 continue;
665 }
666
667 $row['title'] = $tags_trafo->transform($row['title'] ?? '&nbsp;');
668 $row['description'] = $tags_trafo->transform($row['description'] !== '' && $row['description'] !== null ? $row['description'] : '&nbsp;');
669 $row['author'] = $tags_trafo->transform($row['author']);
670 $row['taxonomies'] = $this->loadTaxonomyAssignmentData($row['obj_fi'], $row['question_id']);
671 $row['ttype'] = $this->lng->txt($row['type_tag']);
672
673 $this->questions[ $row['question_id'] ] = $row;
674 }
675 }
676
677 private function loadTaxonomyAssignmentData($parentObjId, $questionId)
678 {
679 $taxAssignmentData = array();
680
681 foreach ($this->getAvailableTaxonomyIds() as $taxId) {
682 require_once 'Services/Taxonomy/classes/class.ilTaxonomyTree.php';
683 require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
684
685 $taxTree = new ilTaxonomyTree($taxId);
686
687 $taxAssignment = new ilTaxNodeAssignment('qpl', $parentObjId, 'quest', $taxId);
688
689 $assignments = $taxAssignment->getAssignmentsOfItem($questionId);
690
691 foreach ($assignments as $assData) {
692 if (!isset($taxAssignmentData[ $assData['tax_id'] ])) {
693 $taxAssignmentData[ $assData['tax_id'] ] = array();
694 }
695
696 $nodeData = $taxTree->getNodeData($assData['node_id']);
697
698 $assData['node_lft'] = $nodeData['lft'];
699
700 $taxAssignmentData[ $assData['tax_id'] ][ $assData['node_id'] ] = $assData;
701 }
702 }
703
704 return $taxAssignmentData;
705 }
706
707 private function isActiveQuestionType($questionData)
708 {
709 if (!isset($questionData['plugin'])) {
710 return false;
711 }
712
713 if (!$questionData['plugin']) {
714 return true;
715 }
716
717 return $this->pluginAdmin->isActive(IL_COMP_MODULE, 'TestQuestionPool', 'qst', $questionData['plugin_name']);
718 }
719
720 public function getDataArrayForQuestionId($questionId)
721 {
722 return $this->questions[$questionId];
723 }
724
725 public function getQuestionDataArray()
726 {
727 return $this->questions;
728 }
729
730 public function isInList($questionId)
731 {
732 return isset($this->questions[$questionId]);
733 }
734
744 public function getTitle($a_comp_id, $a_item_type, $a_item_id)
745 {
746 if ($a_comp_id != 'qpl' || $a_item_type != 'quest' || !(int) $a_item_id) {
747 return '';
748 }
749
750 if (!isset($this->questions[$a_item_id])) {
751 return '';
752 }
753
754 return $this->questions[$a_item_id]['title'];
755 }
756
757 private function checkFilters()
758 {
759 if (strlen($this->getAnswerStatusFilter()) && !$this->getAnswerStatusActiveId()) {
760 require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
761
763 'No active id given! You cannot use the answer status filter without giving an active id.'
764 );
765 }
766 }
767}
An exception for terminatinating execution or to throw for unit testing.
Builds data types.
Definition: Factory.php:20
const IL_COMP_MODULE
setAnswerStatusActiveId($answerStatusActiveId)
const ANSWER_STATUS_FILTER_ALL_NON_CORRECT
answer status filter value domain
setExcludeQuestionIdsFilter($excludeQuestionIdsFilter)
getDataArrayForQuestionId($questionId)
setParentObjectType($parentObjType)
getTaxItems($parentType, $parentObjId, $taxId, $taxNode)
setIncludeQuestionIdsFilter($questionIdsFilter)
__construct(ilDBInterface $db, ilLanguage $lng, ILIAS\Refinery\Factory $refinery, ilPluginAdmin $pluginAdmin)
setQuestionCompletionStatusFilter($questionCompletionStatusFilter)
setForcedQuestionIds($forcedQuestionIds)
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
isActiveQuestionType($questionData)
setAvailableTaxonomyIds($availableTaxonomyIds)
getJoinObjectData()
Get if object data table should be joined.
setAnswerStatusFilter($answerStatusFilter)
setJoinObjectData($a_val)
Set if object data table should be joined.
setParentObjIdsFilter($parentObjIdsFilter)
loadTaxonomyAssignmentData($parentObjId, $questionId)
setQuestionInstanceTypeFilter($questionInstanceTypeFilter)
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
addFieldFilter($fieldName, $fieldValue)
getTitle($a_comp_id, $a_item_type, $a_item_id)
Get title of an assigned item.
static completeMissingPluginName($questionTypeData)
language handling
Administration class for plugins.
Taxonomy node <-> item assignment.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface for assigned items of taxonomies.
Class ChatMainBarProvider \MainMenu\Provider.
$query
foreach($_POST as $key=> $value) $res