ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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 
16 {
22  protected $db = null;
23 
29  private $lng = null;
30 
36  private $pluginAdmin = null;
37 
43  private $parentObjIdsFilter = array();
44 
50  private $parentObjId = null;
51 
57  private $parentObjType = 'qpl';
58 
64  private $availableTaxonomyIds = array();
65 
71  private $fieldFilters = array();
72 
78  private $taxFilters = array();
79 
85  private $taxParentIds = array();
86 
92  private $taxParentTypes = array();
93 
99  private $answerStatusActiveId = null;
100 
104  private $forcedQuestionIds = array();
105 
110  protected $join_obj_data = true;
111 
112 
116  const QUESTION_ANSWER_STATUS_NON_ANSWERED = 'nonAnswered';
117  const QUESTION_ANSWER_STATUS_WRONG_ANSWERED = 'wrongAnswered';
118  const QUESTION_ANSWER_STATUS_CORRECT_ANSWERED = 'correctAnswered';
119 
123  const ANSWER_STATUS_FILTER_ALL_NON_CORRECT = 'allNonCorrect';
126 
132  private $answerStatusFilter = null;
133 
134  const QUESTION_INSTANCE_TYPE_ORIGINALS = 'QST_INSTANCE_TYPE_ORIGINALS';
135  const QUESTION_INSTANCE_TYPE_DUPLICATES = 'QST_INSTANCE_TYPE_DUPLICATES';
136  private $questionInstanceTypeFilter = self::QUESTION_INSTANCE_TYPE_ORIGINALS;
137 
140 
143  const QUESTION_COMPLETION_STATUS_BOTH = 'complete/incomplete';
144  private $questionCompletionStatusFilter = self::QUESTION_COMPLETION_STATUS_BOTH;
145 
151  protected $questions = array();
152 
162  {
163  $this->db = $db;
164  $this->lng = $lng;
165  $this->pluginAdmin = $pluginAdmin;
166  }
167 
168  public function getParentObjId()
169  {
170  return $this->parentObjId;
171  }
172 
173  public function setParentObjId($parentObjId)
174  {
175  $this->parentObjId = $parentObjId;
176  }
177 
178  public function getParentObjectType()
179  {
180  return $this->parentObjType;
181  }
182 
184  {
185  $this->parentObjType = $parentObjType;
186  }
187 
191  public function getParentObjIdsFilter()
192  {
194  }
195 
200  {
201  $this->parentObjIdsFilter = $parentObjIdsFilter;
202  }
203 
205  {
206  $this->questionInstanceTypeFilter = $questionInstanceTypeFilter;
207  }
208 
210  {
212  }
213 
214  public function setIncludeQuestionIdsFilter($questionIdsFilter)
215  {
216  $this->includeQuestionIdsFilter = $questionIdsFilter;
217  }
218 
219  public function getIncludeQuestionIdsFilter()
220  {
222  }
223 
224  public function getExcludeQuestionIdsFilter()
225  {
227  }
228 
230  {
231  $this->excludeQuestionIdsFilter = $excludeQuestionIdsFilter;
232  }
233 
235  {
237  }
238 
240  {
241  $this->questionCompletionStatusFilter = $questionCompletionStatusFilter;
242  }
243 
244  public function addFieldFilter($fieldName, $fieldValue)
245  {
246  $this->fieldFilters[$fieldName] = $fieldValue;
247  }
248 
249  public function addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
250  {
251  $this->taxFilters[$taxId] = $taxNodes;
252  $this->taxParentIds[$taxId] = $parentObjId;
253  $this->taxParentTypes[$taxId] = $parentObjType;
254  }
255 
257  {
258  $this->availableTaxonomyIds = $availableTaxonomyIds;
259  }
260 
261  public function getAvailableTaxonomyIds()
262  {
264  }
265 
267  {
268  $this->answerStatusActiveId = $answerStatusActiveId;
269  }
270 
271  public function getAnswerStatusActiveId()
272  {
274  }
275 
277  {
278  $this->answerStatusFilter = $answerStatusFilter;
279  }
280 
281  public function getAnswerStatusFilter()
282  {
284  }
285 
291  public function setJoinObjectData($a_val)
292  {
293  $this->join_obj_data = $a_val;
294  }
295 
301  public function getJoinObjectData()
302  {
303  return $this->join_obj_data;
304  }
305 
310  {
311  $this->forcedQuestionIds = $forcedQuestionIds;
312  }
313 
317  public function getForcedQuestionIds()
318  {
320  }
321 
322  private function getParentObjFilterExpression()
323  {
324  if( $this->getParentObjId() )
325  {
326  return 'qpl_questions.obj_fi = '.$this->db->quote($this->getParentObjId(), 'integer');
327  }
328 
329  if( count($this->getParentObjIdsFilter()) )
330  {
331  return $this->db->in('qpl_questions.obj_fi', $this->getParentObjIdsFilter(), false, 'integer');
332  }
333 
334  return null;
335  }
336 
337  private function getFieldFilterExpressions()
338  {
339  $expressions = array();
340 
341  foreach($this->fieldFilters as $fieldName => $fieldValue)
342  {
343  switch($fieldName)
344  {
345  case 'title':
346  case 'description':
347  case 'author':
348 
349  $expressions[] = $this->db->like('qpl_questions.' . $fieldName, 'text', "%%$fieldValue%%");
350  break;
351 
352  case 'type':
353 
354  $expressions[] = "qpl_qst_type.type_tag = {$this->db->quote($fieldValue, 'text')}";
355  break;
356 
357  case 'question_id':
358  if ($fieldValue != "" && !is_array($fieldValue))
359  {
360  $fieldValue = array($fieldValue);
361  }
362  $expressions[] = $this->db->in("qpl_questions.question_id", $fieldValue, false, "integer");
363  break;
364 
365  case 'parent_title':
366  if ($this->join_obj_data)
367  {
368  $expressions[] = $this->db->like('object_data.title', 'text', "%%$fieldValue%%");
369  }
370  break;
371  }
372  }
373 
374  return $expressions;
375  }
376 
377  private function getTaxonomyFilterExpressions()
378  {
379  $expressions = array();
380 
381  require_once 'Services/Taxonomy/classes/class.ilTaxonomyTree.php';
382  require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
383 
384  foreach($this->taxFilters as $taxId => $taxNodes)
385  {
386  $questionIds = array();
387 
388  $forceBypass = true;
389 
390  foreach($taxNodes as $taxNode)
391  {
392  $forceBypass = false;
393 
394  $taxItemsByTaxParent = $this->getTaxItems(
395  $this->taxParentTypes[$taxId], $this->taxParentIds[$taxId],
396  $taxId, $taxNode
397  );
398 
399  $taxItemsByParent = $this->getTaxItems(
400  $this->parentObjType, $this->parentObjId,
401  $taxId, $taxNode
402  );
403 
404  $taxItems = array_merge($taxItemsByTaxParent, $taxItemsByParent);
405  foreach($taxItems as $taxItem)
406  {
407  $questionIds[$taxItem['item_id']] = $taxItem['item_id'];
408  }
409  }
410 
411  if( !$forceBypass )
412  {
413  $expressions[] = $this->db->in('question_id', $questionIds, false, 'integer');
414  }
415  }
416 
417  return $expressions;
418  }
419 
427  protected function getTaxItems($parentType, $parentObjId, $taxId, $taxNode)
428  {
429  $taxTree = new ilTaxonomyTree($taxId);
430 
431  $taxNodeAssignment = new ilTaxNodeAssignment(
432  $parentType, $parentObjId, 'quest', $taxId
433  );
434 
435  $subNodes = $taxTree->getSubTreeIds($taxNode);
436  $subNodes[] = $taxNode;
437 
438  return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
439  }
440 
442  {
443  switch( $this->getQuestionInstanceTypeFilter() )
444  {
445  case self::QUESTION_INSTANCE_TYPE_ORIGINALS:
446 
447  return 'qpl_questions.original_id IS NULL';
448 
449  case self::QUESTION_INSTANCE_TYPE_DUPLICATES:
450 
451  return 'qpl_questions.original_id IS NOT NULL';
452  }
453 
454  return null;
455  }
456 
458  {
459  $expressions = array();
460 
461  if( is_array($this->getIncludeQuestionIdsFilter()) )
462  {
463  $expressions[] = $this->db->in(
464  'qpl_questions.question_id', $this->getIncludeQuestionIdsFilter(), false, 'integer'
465  );
466  }
467 
468  if( is_array($this->getExcludeQuestionIdsFilter()) )
469  {
470  $IN = $this->db->in(
471  'qpl_questions.question_id', $this->getExcludeQuestionIdsFilter(), true, 'integer'
472  );
473 
474  if($IN == ' 1=2 ') { $IN = ' 1=1 '; } // required for ILIAS < 5.0
475 
476  $expressions[] = $IN;
477  }
478 
479  return $expressions;
480  }
481 
483  {
484  if( $this->parentObjId )
485  {
486  return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, 'integer')}";
487  }
488 
489  return null;
490  }
491 
493  {
494  $expressions = array();
495 
496  switch( $this->getAnswerStatusFilter() )
497  {
498  case self::ANSWER_STATUS_FILTER_ALL_NON_CORRECT:
499 
500  $expressions[] = '
501  (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
502  ';
503  break;
504 
505  case self::ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY:
506 
507  $expressions[] = 'tst_test_result.question_fi IS NULL';
508  break;
509 
510  case self::ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY:
511 
512  $expressions[] = 'tst_test_result.question_fi IS NOT NULL';
513  $expressions[] = 'tst_test_result.points < qpl_questions.points';
514  break;
515  }
516 
517  return $expressions;
518  }
519 
520  private function getTableJoinExpression()
521  {
522  $tableJoin = "
523  INNER JOIN qpl_qst_type
524  ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi
525  ";
526 
527  if ($this->join_obj_data)
528  {
529  $tableJoin .= "
530  INNER JOIN object_data
531  ON object_data.obj_id = qpl_questions.obj_fi
532  ";
533  }
534 
535  if( $this->getAnswerStatusActiveId() )
536  {
537  $tableJoin .= "
538  LEFT JOIN tst_test_result
539  ON tst_test_result.question_fi = qpl_questions.question_id
540  AND tst_test_result.active_fi = {$this->db->quote($this->getAnswerStatusActiveId(), 'integer')}
541  ";
542  }
543 
544  return $tableJoin;
545  }
546 
548  {
549  $CONDITIONS = array();
550 
551  if( $this->getQuestionInstanceTypeFilterExpression() !== null )
552  {
553  $CONDITIONS[] = $this->getQuestionInstanceTypeFilterExpression();
554  }
555 
556  if( $this->getParentObjFilterExpression() !== null )
557  {
558  $CONDITIONS[] = $this->getParentObjFilterExpression();
559  }
560 
561  if( $this->getParentObjectIdFilterExpression() !== null )
562  {
563  $CONDITIONS[] = $this->getParentObjectIdFilterExpression();
564  }
565 
566  $CONDITIONS = array_merge($CONDITIONS,
568  $this->getFieldFilterExpressions(),
571  );
572 
573  $CONDITIONS = implode(' AND ', $CONDITIONS);
574 
575  return strlen($CONDITIONS) ? 'AND '.$CONDITIONS : '';
576  }
577 
578  private function getSelectFieldsExpression()
579  {
580  $selectFields = array(
581  'qpl_questions.*',
582  'qpl_qst_type.type_tag',
583  'qpl_qst_type.plugin',
584  'qpl_questions.points max_points'
585  );
586 
587  if ($this->join_obj_data)
588  {
589  $selectFields[] = 'object_data.title parent_title';
590  }
591 
592  if( $this->getAnswerStatusActiveId() )
593  {
594  $selectFields[] = 'tst_test_result.points reached_points';
595  $selectFields[] = "CASE
596  WHEN tst_test_result.points IS NULL THEN '".self::QUESTION_ANSWER_STATUS_NON_ANSWERED."'
597  WHEN tst_test_result.points < qpl_questions.points THEN '".self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED."'
598  ELSE '".self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED."'
599  END question_answer_status
600  ";
601  }
602 
603  $selectFields = implode(",\n\t\t\t\t", $selectFields);
604 
605  return "
606  SELECT {$selectFields}
607  ";
608  }
609 
610  private function buildBasicQuery()
611  {
612  return "
613  {$this->getSelectFieldsExpression()}
614 
615  FROM qpl_questions
616 
617  {$this->getTableJoinExpression()}
618 
619  WHERE qpl_questions.tstamp > 0
620  ";
621  }
622 
623  private function buildQuery()
624  {
625  $query = $this->buildBasicQuery()."
626  {$this->getConditionalFilterExpression()}
627  ";
628 
629  if( count($this->getForcedQuestionIds()) )
630  {
631  $query .= "
632  UNION {$this->buildBasicQuery()}
633  AND {$this->db->in('qpl_questions.question_id', $this->getForcedQuestionIds(), false, 'integer')}
634  ";
635  }
636 
637  return $query;
638  }
639 
640  public function load()
641  {
642  $this->checkFilters();
643 
644  $query = $this->buildQuery();
645 
646  #vd($query);
647 
648  $res = $this->db->query($query);
649 
650  //echo $this->db->db->last_query;
651 
652  #vd($this->db->db->last_query);
653 
654  while( $row = $this->db->fetchAssoc($res) )
655  {
656  if( !$this->isActiveQuestionType($row) )
657  {
658  continue;
659  }
660 
661  $row['taxonomies'] = $this->loadTaxonomyAssignmentData($row['obj_fi'], $row['question_id']);
662 
663  $row['ttype'] = $this->lng->txt($row['type_tag']);
664 
665  $this->questions[ $row['question_id'] ] = $row;
666  }
667  }
668 
669  private function loadTaxonomyAssignmentData($parentObjId, $questionId)
670  {
671  $taxAssignmentData = array();
672 
673  foreach($this->getAvailableTaxonomyIds() as $taxId)
674  {
675  require_once 'Services/Taxonomy/classes/class.ilTaxonomyTree.php';
676  require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
677 
678  $taxTree = new ilTaxonomyTree($taxId);
679 
680  $taxAssignment = new ilTaxNodeAssignment('qpl', $parentObjId, 'quest', $taxId);
681 
682  $assignments = $taxAssignment->getAssignmentsOfItem($questionId);
683 
684  foreach($assignments as $assData)
685  {
686  if( !isset($taxAssignmentData[ $assData['tax_id'] ]) )
687  {
688  $taxAssignmentData[ $assData['tax_id'] ] = array();
689  }
690 
691  $nodeData = $taxTree->getNodeData($assData['node_id']);
692 
693  $assData['node_lft'] = $nodeData['lft'];
694 
695  $taxAssignmentData[ $assData['tax_id'] ][ $assData['node_id'] ] = $assData;
696  }
697  }
698 
699  return $taxAssignmentData;
700  }
701 
702  private function isActiveQuestionType($questionData)
703  {
704  if( !isset($questionData['plugin']) )
705  {
706  return false;
707  }
708 
709  if( !$questionData['plugin'] )
710  {
711  return true;
712  }
713 
714  return $this->pluginAdmin->isActive(IL_COMP_MODULE, 'TestQuestionPool', 'qst', $questionData['type_tag']);
715  }
716 
717  public function getDataArrayForQuestionId($questionId)
718  {
719  return $this->questions[$questionId];
720  }
721 
722  public function getQuestionDataArray()
723  {
724  return $this->questions;
725  }
726 
727  public function isInList($questionId)
728  {
729  return isset($this->questions[$questionId]);
730  }
731 
741  public function getTitle($a_comp_id, $a_item_type, $a_item_id)
742  {
743  if( $a_comp_id != 'qpl' || $a_item_type != 'quest' || !(int)$a_item_id )
744  {
745  return '';
746  }
747 
748  if( !isset($this->questions[$a_item_id]) )
749  {
750  return '';
751  }
752 
753  return $this->questions[$a_item_id]['title'];
754  }
755 
756  private function checkFilters()
757  {
758  if( strlen($this->getAnswerStatusFilter()) && !$this->getAnswerStatusActiveId() )
759  {
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  }
768 }
setAnswerStatusFilter($answerStatusFilter)
Taxonomy node <-> item assignment.
getTaxItems($parentType, $parentObjId, $taxId, $taxNode)
setExcludeQuestionIdsFilter($excludeQuestionIdsFilter)
getDataArrayForQuestionId($questionId)
isActiveQuestionType($questionData)
setAnswerStatusActiveId($answerStatusActiveId)
Administration class for plugins.
setIncludeQuestionIdsFilter($questionIdsFilter)
setParentObjectType($parentObjType)
const IL_COMP_MODULE
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
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.
__construct(ilDB $db, ilLanguage $lng, ilPluginAdmin $pluginAdmin)
Constructor.
setForcedQuestionIds($forcedQuestionIds)
Database Wrapper.
Definition: class.ilDB.php:28
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)
language handling
loadTaxonomyAssignmentData($parentObjId, $questionId)
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
setQuestionCompletionStatusFilter($questionCompletionStatusFilter)
setAvailableTaxonomyIds($availableTaxonomyIds)