83 private ?NotesService $notes_service =
null
127 $this->includeQuestionIdsFilter = $questionIdsFilter;
137 $this->fieldFilters[$fieldName] = $fieldValue;
142 $this->taxFilters[$taxId] = $taxNodes;
149 $this->taxFiltersExcludeAnyObjectsWithTaxonomies = $flag;
172 $this->join_obj_data = $a_val;
177 if ($this->parentObjId) {
178 return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, ilDBConstants::T_INTEGER)}";
181 if (!empty($this->parentObjIdsFilter)) {
192 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
193 switch ($fieldName) {
198 $expressions[] = $this->db->like(
"qpl_questions.$fieldName",
ilDBConstants::T_TEXT,
"%%$fieldValue%%");
201 $expressions[] =
"qpl_qst_type.type_tag = {$this->db->quote($fieldValue, ilDBConstants::T_TEXT)}";
204 if ($fieldValue !==
'' && !is_array($fieldValue)) {
205 $fieldValue = [$fieldValue];
210 if ($this->join_obj_data) {
222 $feedback_join = match ($this->fieldFilters[
'feedback'] ??
null) {
228 if (isset($feedback_join)) {
229 $sql =
"{$feedback_join} JOIN qpl_fb_generic ON qpl_fb_generic.question_fi = qpl_questions.question_id ";
230 $table_join .= !str_contains($table_join, $sql) ? $sql :
'';
240 $taxonomy_title = $this->fieldFilters[
'taxonomy_title'] ??
'';
241 $taxonomy_node_title = $this->fieldFilters[
'taxonomy_node_title'] ??
'';
243 if ($taxonomy_title ===
'' && $taxonomy_node_title ===
'') {
247 $base =
'SELECT DISTINCT item_id FROM tax_node_assignment';
249 $like_taxonomy_title = $taxonomy_title !==
''
250 ?
"AND {$this->db->like('object_data.title', ilDBConstants::T_TEXT, "%$taxonomy_title%
", false)}"
252 $like_taxonomy_node_title = $taxonomy_node_title !==
''
253 ?
"AND {$this->db->like('tax_node.title', ilDBConstants::T_TEXT, "%$taxonomy_node_title%
", false)}"
256 $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)";
257 $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)";
259 $expressions[] =
"qpl_questions.question_id IN ($base $inner_join_object_data $inner_join_tax_node)";
266 if ($this->taxFiltersExcludeAnyObjectsWithTaxonomies) {
267 return [
'question_id NOT IN (SELECT DISTINCT item_id FROM tax_node_assignment)'];
271 foreach ($this->taxFilters as $tax_id => $tax_nodes) {
274 if ($tax_nodes === []) {
278 foreach ($tax_nodes as $tax_node) {
280 $this->taxParentTypes[$tax_id],
281 $this->taxParentIds[$tax_id],
287 $this->parentObjType,
293 $tax_items = array_merge($tax_items_by_tax_parent, $tax_items_by_parent);
294 foreach ($tax_items as $tax_item) {
295 $question_ids[$tax_item[
'item_id']] = $tax_item[
'item_id'];
316 $subNodes = $taxTree->getSubTreeIds($taxNode);
317 $subNodes[] = $taxNode;
319 return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
324 return match ($this->questionInstanceTypeFilter) {
325 self::QUESTION_INSTANCE_TYPE_ORIGINALS =>
'qpl_questions.original_id IS NULL',
326 self::QUESTION_INSTANCE_TYPE_DUPLICATES =>
'qpl_questions.original_id IS NOT NULL',
335 if (!empty($this->includeQuestionIdsFilter)) {
336 $expressions[] = $this->db->in(
337 'qpl_questions.question_id',
338 $this->includeQuestionIdsFilter,
344 if (!empty($this->excludeQuestionIdsFilter)) {
346 'qpl_questions.question_id',
347 $this->excludeQuestionIdsFilter,
352 $expressions[] = $IN ===
' 1=2 ' ?
' 1=1 ' : $IN;
360 return match ($this->answerStatusFilter) {
361 self::ANSWER_STATUS_FILTER_ALL_NON_CORRECT => [
'
362 (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
364 self::ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY => [
'tst_test_result.question_fi IS NULL'],
365 self::ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY => [
366 'tst_test_result.question_fi IS NOT NULL',
367 'tst_test_result.points < qpl_questions.points'
375 $table_join =
'INNER JOIN qpl_qst_type ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi ';
377 if ($this->join_obj_data) {
379 INNER JOIN object_data ON object_data.obj_id = qpl_questions.obj_fi
380 INNER JOIN object_reference ON object_reference.obj_id = object_data.obj_id
385 $this->parentObjType ===
'tst'
386 && $this->questionInstanceTypeFilter === self::QUESTION_INSTANCE_TYPE_ALL
388 $table_join .=
'INNER JOIN tst_test_question tstquest ON tstquest.question_fi = qpl_questions.question_id';
393 if ($this->answerStatusActiveId) {
395 LEFT JOIN tst_test_result
396 ON tst_test_result.question_fi = qpl_questions.question_id
397 AND tst_test_result.active_fi = {$this->db->quote($this->answerStatusActiveId, ilDBConstants::T_INTEGER)}
416 $conditions = array_merge(
424 $conditions = implode(
' AND ', $conditions);
425 return $conditions !==
'' ?
"AND $conditions" :
'';
432 'qpl_qst_type.type_tag AS question_type',
433 'qpl_qst_type.plugin',
434 'qpl_qst_type.plugin_name',
435 'qpl_questions.points max_points'
438 if ($this->join_obj_data) {
439 $select_fields[] =
'object_data.title parent_title, object_data.type parent_type, object_reference.ref_id parent_ref_id';
442 if ($this->answerStatusActiveId) {
443 $select_fields[] =
'tst_test_result.points reached_points';
444 $select_fields[] =
"CASE
445 WHEN tst_test_result.points IS NULL THEN '" . self::QUESTION_ANSWER_STATUS_NON_ANSWERED .
"'
446 WHEN tst_test_result.points < qpl_questions.points THEN '" . self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED .
"'
447 ELSE '" . self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED .
"'
448 END question_answer_status
456 $select_fields = implode(
', ', $select_fields);
457 return "SELECT DISTINCT $select_fields";
463 $tables = [
'qpl_fb_generic',
'qpl_fb_specific'];
465 foreach ($tables as $table) {
466 $subquery =
"SELECT 1 FROM $table WHERE $table.question_fi = qpl_questions.question_id AND $table.feedback <> ''";
467 $cases[] =
"WHEN EXISTS ($subquery) THEN TRUE";
470 $page_object_table =
'page_object';
471 foreach ($tables as $table) {
473 "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",
477 $cases[] =
"WHEN EXISTS ($subquery) THEN TRUE";
480 $feedback_case_subquery = implode(
' ', $cases);
481 return "CASE $feedback_case_subquery ELSE FALSE END AS feedback";
486 $tax_node_assignment_table =
'tax_node_assignment';
487 $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'";
488 return "CASE WHEN EXISTS ($tax_subquery) THEN TRUE ELSE FALSE END AS taxonomies";
493 return "{$this->getSelectFieldsExpression()} FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0";
500 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
501 if ($fieldName ===
'feedback') {
502 $fieldValue = strtoupper($fieldValue);
503 if (in_array($fieldValue, [
'TRUE',
'FALSE'],
true)) {
504 $expressions[] =
"feedback IS $fieldValue";
511 $having = implode(
' AND ', $expressions);
512 return $having !==
'' ?
"HAVING $having" :
'';
517 return $this->order_field ===
null || $this->order_direction ===
null || $this->order_field ===
'question_type'
519 :
" ORDER BY `$this->order_field` $this->order_direction";
524 if ($this->order_field ===
'question_type'
525 || $this->range ===
null) {
529 $limit = max($this->range->getLength(), 0);
530 $offset = max($this->range->getStart(), 0);
532 return " LIMIT {$limit} OFFSET {$offset}";
537 if ($order ===
null) {
538 return [
'order_field' =>
null,
'order_direction' =>
null];
543 static fn(
string $index,
string $key,
string $value): array => [
558 return implode(PHP_EOL, array_filter([
571 $tags_trafo = $this->
refinery->encode()->htmlSpecialCharsAsEntities();
574 while ($row_from_db = $this->db->fetchAssoc(
$res)) {
581 $row[
'title'] = $tags_trafo->transform($row[
'title'] ??
' ');
582 $row[
'description'] = $tags_trafo->transform($row[
'description'] ??
'');
583 $row[
'author'] = $tags_trafo->transform($row[
'author']);
585 $row[
'question_type'] = $this->
lng->txt($row[
'question_type']);
586 $row[
'feedback'] = $row[
'feedback'] === 1;
590 $this->filter_comments === self::QUESTION_COMMENTED_ONLY && $row[
'comments'] === 0
591 || $this->filter_comments === self::QUESTION_COMMENTED_EXCLUDED && $row[
'comments'] > 0
596 $this->questions[$row[
'question_id']] = $row;
599 if ($this->order_field ===
'question_type') {
607 $direction = $this->order_direction ===
Order::DESC ? -1 : 1;
610 fn(array
$a, array
$b):
int => $direction * (
$a[
'question_type'] <=>
$b[
'question_type'])
613 if ($this->range !==
null) {
616 $this->range->getStart(),
617 $this->range->getLength()
625 mixed $additional_viewcontrol_data,
627 mixed $additional_parameters
629 $this->checkFilters();
632 $query =
"SELECT $count FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0 {$this->getConditionalFilterExpression()}";
634 return (
int) ($this->db->query($query)->fetch()[$count] ?? 0);
639 if ($this->notes_service ===
null) {
642 $notes_context = $this->notes_service->data()->context(
643 $this->getParentObjId(),
647 return $this->notes_service->domain()->getNrOfCommentsForContext($notes_context);
652 $this->filter_comments = $commented;
659 $tax_assignment_data = [];
660 foreach ($this->availableTaxonomyIds as $tax_id) {
664 $assignments = $tax_assignment->getAssignmentsOfItem($question_id);
666 foreach ($assignments as $ass_data) {
667 if (!isset($tax_assignment_data[$ass_data[
'tax_id']])) {
668 $tax_assignment_data[$ass_data[
'tax_id']] = [];
671 $ass_data[
'node_lft'] = $tax_tree->getNodeData($ass_data[
'node_id']);
673 $tax_assignment_data[$ass_data[
'tax_id']][$ass_data[
'node_id']] = $ass_data;
677 return $tax_assignment_data;
682 if (!isset($questionData[
'plugin'])) {
686 if (!$questionData[
'plugin']) {
691 !isset($questionData[
'plugin_name'])
692 || !$this->component_repository->getComponentByTypeAndName(
695 )->getPluginSlotById(
'qst')->hasPluginName($questionData[
'plugin_name'])
700 return $this->component_repository
702 ->getPluginSlotById(
'qst')
703 ->getPluginByName($questionData[
'plugin_name'])
709 return $this->questions[$questionId];
714 return $this->questions;
719 return isset($this->questions[$questionId]);
731 public function getTitle(
string $a_comp_id,
string $a_item_type,
int $a_item_id): string
733 if ($a_comp_id !==
'qpl' || $a_item_type !==
'quest' || !$a_item_id) {
737 return $this->questions[$a_item_id][
'title'] ??
'';
742 if ($this->answerStatusFilter !==
'' && !$this->answerStatusActiveId) {
744 'No active id given! You cannot use the answer status filter without giving an active id.'
Both the subject and the direction need to be specified when expressing an order.
join($init, callable $fn)
A simple class to express a naive range of whole positive numbers.
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)
string $questionInstanceTypeFilter
array $parentObjIdsFilter
setParentObjId(?int $parentObjId)
getFilterByAssignedTaxonomyIdsExpression()
buildLimitQueryExpression()
const ANSWER_STATUS_FILTER_ALL_NON_CORRECT
answer status filter value domain
setCommentFilter(?int $commented=null)
getTotalRowCount(mixed $additional_viewcontrol_data, mixed $filter_data, mixed $additional_parameters)
array $availableTaxonomyIds
getTaxonomyFilterExpressions()
const QUESTION_INSTANCE_TYPE_ALL
setParentObjIdsFilter(array $parentObjIdsFilter)
getQuestionInstanceTypeFilterExpression()
const QUESTION_INSTANCE_TYPE_ORIGINALS
getDataArrayForQuestionId(int $questionId)
array $includeQuestionIdsFilter
const ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY
getParentObjFilterExpression()
addFeedbackJoinIfNeeded(string $table_join)
getNumberOfCommentsForQuestion(int $question_id)
setOrder(?Order $order=null)
loadTaxonomyAssignmentData(int $parent_obj_id, int $question_id)
setIncludeQuestionIdsFilter(array $questionIdsFilter)
buildOrderQueryExpression()
string $answerStatusFilter
bool $taxFiltersExcludeAnyObjectsWithTaxonomies
setAvailableTaxonomyIds(array $availableTaxonomyIds)
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
getFieldFilterExpressions()
setJoinObjectData(bool $a_val)
Set if object data table should be joined.
getHavingFilterExpression()
__construct(private ilDBInterface $db, private ilLanguage $lng, private Refinery $refinery, private ilComponentRepository $component_repository, private ?NotesService $notes_service=null)
setExcludeQuestionIdsFilter(array $excludeQuestionIdsFilter)
getQuestionIdsFilterExpressions()
addFieldFilter(string $fieldName, mixed $fieldValue)
const QUESTION_ANSWER_STATUS_WRONG_ANSWERED
const QUESTION_ANSWER_STATUS_CORRECT_ANSWERED
array $excludeQuestionIdsFilter
getTaxItems(string $parentType, int $parentObjId, int $taxId, int $taxNode)
const QUESTION_INSTANCE_TYPE_DUPLICATES
int $answerStatusActiveId
getOrderFieldAndDirection(?Order $order)
generateFeedbackSubquery()
setAnswerStatusActiveId(?int $answerStatusActiveId)
const ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY
getTitle(string $a_comp_id, string $a_item_type, int $a_item_id)
Get title of an assigned item.
const QUESTION_COMMENTED_EXCLUDED
setRange(?Range $range=null)
orderAndLimitByQuestionType(array $questions)
setParentObjectType(string $parentObjType)
const QUESTION_COMMENTED_ONLY
setAnswerStatusFilter(string $answerStatusFilter)
getSelectFieldsExpression()
generateTaxonomySubquery()
addTaxonomyFilterNoTaxonomySet(bool $flag)
getConditionalFilterExpression()
isInList(int $questionId)
getAnswerStatusFilterExpressions()
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
static completeMissingPluginName(array $question_type_data)
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.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples