ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
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 
4 require_once 'Services/Taxonomy/interfaces/interface.ilTaxAssignedItemInfo.php';
5 require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionType.php';
6 
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 
124  const QUESTION_ANSWER_STATUS_NON_ANSWERED = 'nonAnswered';
125  const QUESTION_ANSWER_STATUS_WRONG_ANSWERED = 'wrongAnswered';
126  const QUESTION_ANSWER_STATUS_CORRECT_ANSWERED = 'correctAnswered';
127 
131  const ANSWER_STATUS_FILTER_ALL_NON_CORRECT = 'allNonCorrect';
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 
146  private $questionInstanceTypeFilter = self::QUESTION_INSTANCE_TYPE_ORIGINALS;
147 
150 
153  const QUESTION_COMPLETION_STATUS_BOTH = 'complete/incomplete';
154  private $questionCompletionStatusFilter = self::QUESTION_COMPLETION_STATUS_BOTH;
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 
180  public function setParentObjId($parentObjId)
181  {
182  $this->parentObjId = $parentObjId;
183  }
184 
185  public function getParentObjectType()
186  {
187  return $this->parentObjType;
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 
226  public function getIncludeQuestionIdsFilter()
227  {
229  }
230 
231  public function getExcludeQuestionIdsFilter()
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  {
310  return $this->join_obj_data;
311  }
312 
317  {
318  $this->forcedQuestionIds = $forcedQuestionIds;
319  }
320 
324  public function getForcedQuestionIds()
325  {
327  }
328 
329  private function getParentObjFilterExpression()
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 
379  private function getTaxonomyFilterExpressions()
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,
435  $parentObjId,
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()) {
449  case self::QUESTION_INSTANCE_TYPE_ORIGINALS:
450  return 'qpl_questions.original_id IS NULL';
451  case self::QUESTION_INSTANCE_TYPE_DUPLICATES:
452  return 'qpl_questions.original_id IS NOT NULL';
453  case self::QUESTION_INSTANCE_TYPE_ALL:
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()) {
506  case self::ANSWER_STATUS_FILTER_ALL_NON_CORRECT:
507 
508  $expressions[] = '
509  (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
510  ';
511  break;
512 
513  case self::ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY:
514 
515  $expressions[] = 'tst_test_result.question_fi IS NULL';
516  break;
517 
518  case self::ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY:
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,
580  $this->getFieldFilterExpressions(),
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 
762  throw new ilTestQuestionPoolException(
763  'No active id given! You cannot use the answer status filter without giving an active id.'
764  );
765  }
766  }
767 }
__construct(ilDBInterface $db, ilLanguage $lng, ILIAS\Refinery\Factory $refinery, ilPluginAdmin $pluginAdmin)
setAnswerStatusFilter($answerStatusFilter)
Taxonomy node <-> item assignment.
Class ChatMainBarProvider .
getTaxItems($parentType, $parentObjId, $taxId, $taxNode)
setExcludeQuestionIdsFilter($excludeQuestionIdsFilter)
getDataArrayForQuestionId($questionId)
isActiveQuestionType($questionData)
setAnswerStatusActiveId($answerStatusActiveId)
Administration class for plugins.
static completeMissingPluginName($questionTypeData)
foreach($_POST as $key=> $value) $res
setIncludeQuestionIdsFilter($questionIdsFilter)
setParentObjectType($parentObjType)
const IL_COMP_MODULE
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
$query
getJoinObjectData()
Get if object data table should be joined.
setQuestionInstanceTypeFilter($questionInstanceTypeFilter)
const ANSWER_STATUS_FILTER_ALL_NON_CORRECT
answer status filter value domain
Interface for assigned items of taxonomies.
setForcedQuestionIds($forcedQuestionIds)
setJoinObjectData($a_val)
Set if object data table should be joined.
getTitle($a_comp_id, $a_item_type, $a_item_id)
Get title of an assigned item.
addFieldFilter($fieldName, $fieldValue)
setParentObjIdsFilter($parentObjIdsFilter)
loadTaxonomyAssignmentData($parentObjId, $questionId)
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
setQuestionCompletionStatusFilter($questionCompletionStatusFilter)
setAvailableTaxonomyIds($availableTaxonomyIds)