118 $this->questionInstanceTypeFilter = (string) $questionInstanceTypeFilter;
123 $this->includeQuestionIdsFilter = $questionIdsFilter;
133 $this->fieldFilters[$fieldName] = $fieldValue;
138 $this->taxFilters[$taxId] = $taxNodes;
145 $this->taxFiltersExcludeAnyObjectsWithTaxonomies = $flag;
168 $this->join_obj_data = $a_val;
173 if ($this->parentObjId) {
174 return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, ilDBConstants::T_INTEGER)}";
177 if (!empty($this->parentObjIdsFilter)) {
188 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
189 switch ($fieldName) {
194 $expressions[] = $this->db->like(
"qpl_questions.$fieldName",
ilDBConstants::T_TEXT,
"%%$fieldValue%%");
197 $expressions[] =
"qpl_qst_type.type_tag = {$this->db->quote($fieldValue, ilDBConstants::T_TEXT)}";
200 if ($fieldValue !==
'' && !is_array($fieldValue)) {
201 $fieldValue = [$fieldValue];
206 if ($this->join_obj_data) {
218 $feedback_join = match ($this->fieldFilters[
'feedback'] ??
null) {
224 if (isset($feedback_join)) {
225 $SQL =
"$feedback_join JOIN qpl_fb_generic ON qpl_fb_generic.question_fi = qpl_questions.question_id ";
226 $tableJoin .= !str_contains($tableJoin, $SQL) ? $SQL :
'';
236 $taxonomy_title = $this->fieldFilters[
'taxonomy_title'] ??
'';
237 $taxonomy_node_title = $this->fieldFilters[
'taxonomy_node_title'] ??
'';
239 if ($taxonomy_title ===
'' && $taxonomy_node_title ===
'') {
243 $base =
'SELECT DISTINCT item_id FROM tax_node_assignment';
245 $like_taxonomy_title = $taxonomy_title !==
'' 246 ?
"AND {$this->db->like('object_data.title', ilDBConstants::T_TEXT, "%$taxonomy_title%
", false)}" 248 $like_taxonomy_node_title = $taxonomy_node_title !==
'' 249 ?
"AND {$this->db->like('tax_node.title', ilDBConstants::T_TEXT, "%$taxonomy_node_title%
", false)}" 252 $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)";
253 $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)";
255 $expressions[] =
"qpl_questions.question_id IN ($base $inner_join_object_data $inner_join_tax_node)";
262 if ($this->taxFiltersExcludeAnyObjectsWithTaxonomies) {
263 return [
'question_id NOT IN (SELECT DISTINCT item_id FROM tax_node_assignment)'];
267 foreach ($this->taxFilters as $tax_id => $tax_nodes) {
270 if ($tax_nodes === []) {
274 foreach ($tax_nodes as $tax_node) {
276 $this->taxParentTypes[$tax_id],
277 $this->taxParentIds[$tax_id],
283 $this->parentObjType,
289 $tax_items = array_merge($tax_items_by_tax_parent, $tax_items_by_parent);
290 foreach ($tax_items as $tax_item) {
291 $question_ids[$tax_item[
'item_id']] = $tax_item[
'item_id'];
301 protected function getTaxItems(
string $parentType,
int $parentObjId,
int $taxId,
int $taxNode): array
312 $subNodes = $taxTree->getSubTreeIds($taxNode);
313 $subNodes[] = $taxNode;
315 return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
320 return match ($this->questionInstanceTypeFilter) {
321 self::QUESTION_INSTANCE_TYPE_ORIGINALS =>
'qpl_questions.original_id IS NULL',
322 self::QUESTION_INSTANCE_TYPE_DUPLICATES =>
'qpl_questions.original_id IS NOT NULL',
331 if (!empty($this->includeQuestionIdsFilter)) {
332 $expressions[] = $this->db->in(
333 'qpl_questions.question_id',
334 $this->includeQuestionIdsFilter,
340 if (!empty($this->excludeQuestionIdsFilter)) {
342 'qpl_questions.question_id',
343 $this->excludeQuestionIdsFilter,
348 $expressions[] = $IN ===
' 1=2 ' ?
' 1=1 ' : $IN;
356 return match ($this->answerStatusFilter) {
357 self::ANSWER_STATUS_FILTER_ALL_NON_CORRECT => [
' 358 (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points) 360 self::ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY => [
'tst_test_result.question_fi IS NULL'],
361 self::ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY => [
362 'tst_test_result.question_fi IS NOT NULL',
363 'tst_test_result.points < qpl_questions.points' 372 INNER JOIN qpl_qst_type 373 ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi 376 if ($this->join_obj_data) {
378 INNER JOIN object_data 379 ON object_data.obj_id = qpl_questions.obj_fi 384 $this->parentObjType ===
'tst' 385 && $this->questionInstanceTypeFilter === self::QUESTION_INSTANCE_TYPE_ALL
387 $tableJoin .=
'INNER JOIN tst_test_question tstquest ON tstquest.question_fi = qpl_questions.question_id';
392 if ($this->answerStatusActiveId) {
394 LEFT JOIN tst_test_result 395 ON tst_test_result.question_fi = qpl_questions.question_id 396 AND tst_test_result.active_fi = {$this->db->quote($this->answerStatusActiveId, ilDBConstants::T_INTEGER)} 415 $conditions = array_merge(
423 $conditions = implode(
' AND ', $conditions);
424 return $conditions !==
'' ?
"AND $conditions" :
'';
431 'qpl_qst_type.type_tag',
432 'qpl_qst_type.plugin',
433 'qpl_qst_type.plugin_name',
434 'qpl_questions.points max_points' 437 if ($this->join_obj_data) {
438 $select_fields[] =
'object_data.title parent_title';
441 if ($this->answerStatusActiveId) {
442 $select_fields[] =
'tst_test_result.points reached_points';
443 $select_fields[] =
"CASE 444 WHEN tst_test_result.points IS NULL THEN '" . self::QUESTION_ANSWER_STATUS_NON_ANSWERED .
"' 445 WHEN tst_test_result.points < qpl_questions.points THEN '" . self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED .
"' 446 ELSE '" . self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED .
"' 447 END question_answer_status 455 $select_fields = implode(
', ', $select_fields);
456 return "SELECT DISTINCT $select_fields";
462 $tables = [
'qpl_fb_generic',
'qpl_fb_specific'];
464 foreach ($tables as $table) {
465 $subquery =
"SELECT 1 FROM $table WHERE $table.question_fi = qpl_questions.question_id AND $table.feedback <> ''";
466 $cases[] =
"WHEN EXISTS ($subquery) THEN TRUE";
469 $page_object_table =
'page_object';
470 foreach ($tables as $table) {
472 "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",
476 $cases[] =
"WHEN EXISTS ($subquery) THEN TRUE";
479 $feedback_case_subquery = implode(
' ', $cases);
480 return "CASE $feedback_case_subquery ELSE FALSE END AS feedback";
485 $tax_node_assignment_table =
'tax_node_assignment';
486 $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'";
487 return "CASE WHEN EXISTS ($tax_subquery) THEN TRUE ELSE FALSE END AS taxonomies";
492 return "{$this->getSelectFieldsExpression()} FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0";
499 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
500 if ($fieldName ===
'feedback') {
501 $fieldValue = strtoupper($fieldValue);
502 if (in_array($fieldValue, [
'TRUE',
'FALSE'],
true)) {
503 $expressions[] =
"feedback IS $fieldValue";
510 $having = implode(
' AND ', $expressions);
511 return $having !==
'' ?
"HAVING $having" :
'';
517 if ($order ===
null) {
521 [$order_field, $order_direction] = $order->
join(
523 static fn(
string $index,
string $key,
string $value): array => [$key, $value]
526 $order_direction = strtoupper($order_direction);
527 if (!in_array($order_direction, [Order::ASC, Order::DESC],
true)) {
528 $order_direction = Order::ASC;
531 return " ORDER BY `$order_field` $order_direction";
537 if ($range ===
null) {
542 $offset = max($range->
getStart(), 0);
544 return " LIMIT $limit OFFSET $offset";
549 return implode(PHP_EOL, array_filter([
562 $tags_trafo = $this->
refinery->encode()->htmlSpecialCharsAsEntities();
565 while ($row = $this->db->fetchAssoc(
$res)) {
572 $row[
'title'] = $tags_trafo->transform($row[
'title'] ??
' ');
573 $row[
'description'] = $tags_trafo->transform($row[
'description'] ??
'');
574 $row[
'author'] = $tags_trafo->transform($row[
'author']);
576 $row[
'ttype'] = $this->
lng->txt($row[
'type_tag']);
577 $row[
'feedback'] = $row[
'feedback'] === 1;
581 $this->filter_comments === self::QUESTION_COMMENTED_ONLY && $row[
'comments'] === 0
582 || $this->filter_comments === self::QUESTION_COMMENTED_EXCLUDED && $row[
'comments'] > 0
587 $this->questions[$row[
'question_id']] = $row;
596 $query =
"SELECT $count FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0 {$this->getConditionalFilterExpression()}";
598 return (
int) ($this->db->query($query)->fetch()[$count] ?? 0);
603 if ($this->notes_service ===
null) {
606 $notes_context = $this->notes_service->data()->context(
611 return $this->notes_service->domain()->getNrOfCommentsForContext($notes_context);
616 $this->filter_comments = $commented;
623 $tax_assignment_data = [];
624 foreach ($this->availableTaxonomyIds as $tax_id) {
628 $assignments = $tax_assignment->getAssignmentsOfItem($question_id);
630 foreach ($assignments as $ass_data) {
631 if (!isset($tax_assignment_data[$ass_data[
'tax_id']])) {
632 $tax_assignment_data[$ass_data[
'tax_id']] = [];
635 $ass_data[
'node_lft'] = $tax_tree->getNodeData($ass_data[
'node_id']);
637 $tax_assignment_data[$ass_data[
'tax_id']][$ass_data[
'node_id']] = $ass_data;
641 return $tax_assignment_data;
646 if (!isset($questionData[
'plugin'])) {
650 if (!$questionData[
'plugin']) {
655 !isset($questionData[
'plugin_name'])
656 || !$this->component_repository->getComponentByTypeAndName(
659 )->getPluginSlotById(
'qst')->hasPluginName($questionData[
'plugin_name'])
664 return $this->component_repository
666 ->getPluginSlotById(
'qst')
667 ->getPluginByName($questionData[
'plugin_name'])
673 return $this->questions[$questionId];
683 return isset($this->questions[$questionId]);
695 public function getTitle(
string $a_comp_id,
string $a_item_type,
int $a_item_id): string
697 if ($a_comp_id !==
'qpl' || $a_item_type !==
'quest' || !$a_item_id) {
701 return $this->questions[$a_item_id][
'title'] ??
'';
706 if ($this->answerStatusFilter !==
'' && !$this->answerStatusActiveId) {
708 'No active id given! You cannot use the answer status filter without giving an active id.'
static completeMissingPluginName(array $question_type_data)
setExcludeQuestionIdsFilter(array $excludeQuestionIdsFilter)
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...
setQuestionInstanceTypeFilter(?string $questionInstanceTypeFilter)
join($init, callable $fn)
isActiveQuestionType(array $questionData)
getSelectFieldsExpression()
int $answerStatusActiveId
setAvailableTaxonomyIds(array $availableTaxonomyIds)
setJoinObjectData(bool $a_val)
Set if object data table should be joined.
getQuestionIdsFilterExpressions()
isInList(int $questionId)
setOrder(?Order $order=null)
setParentObjIdsFilter(array $parentObjIdsFilter)
setParentObjectType(string $parentObjType)
getParentObjFilterExpression()
setIncludeQuestionIdsFilter(array $questionIdsFilter)
const QUESTION_COMMENTED_ONLY
const QUESTION_COMMENTED_EXCLUDED
const QUESTION_INSTANCE_TYPE_DUPLICATES
array $availableTaxonomyIds
getTitle(string $a_comp_id, string $a_item_type, int $a_item_id)
Get title of an assigned item.
buildOrderQueryExpression()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setRange(?Range $range=null)
Both the subject and the direction need to be specified when expressing an order. ...
getConditionalFilterExpression()
addTaxonomyFilterNoTaxonomySet(bool $flag)
bool $taxFiltersExcludeAnyObjectsWithTaxonomies
__construct(private ilDBInterface $db, private ilLanguage $lng, private Refinery $refinery, private ilComponentRepository $component_repository, private ?NotesService $notes_service=null)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
const ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY
getDataArrayForQuestionId(int $questionId)
addFieldFilter(string $fieldName, mixed $fieldValue)
getTaxonomyFilterExpressions()
getFilterByAssignedTaxonomyIdsExpression()
getTotalRowCount(?array $filter_data, ?array $additional_parameters)
getAnswerStatusFilterExpressions()
array $excludeQuestionIdsFilter
string $questionInstanceTypeFilter
const PAGE_OBJECT_TYPE_GENERIC_FEEDBACK
type for generic feedback page objects
loadTaxonomyAssignmentData(int $parent_obj_id, int $question_id)
getFieldFilterExpressions()
addTaxonomyFilter($taxId, $taxNodes, $parentObjId, $parentObjType)
array $includeQuestionIdsFilter
getHavingFilterExpression()
getNumberOfCommentsForQuestion(int $question_id)
const ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY
getQuestionInstanceTypeFilterExpression()
const ANSWER_STATUS_FILTER_ALL_NON_CORRECT
answer status filter value domain
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
generateFeedbackSubquery()
array $parentObjIdsFilter
const QUESTION_ANSWER_STATUS_CORRECT_ANSWERED
setParentObjId(?int $parentObjId)
const QUESTION_ANSWER_STATUS_WRONG_ANSWERED
string $answerStatusFilter
generateTaxonomySubquery()
setAnswerStatusFilter(string $answerStatusFilter)
handleFeedbackJoin(string $tableJoin)
const QUESTION_INSTANCE_TYPE_ALL
A simple class to express a naive range of whole positive numbers.
setCommentFilter(?int $commented=null)
buildLimitQueryExpression()
setAnswerStatusActiveId(?int $answerStatusActiveId)
const QUESTION_INSTANCE_TYPE_ORIGINALS
const QUESTION_ANSWER_STATUS_NON_ANSWERED
answer status domain for single questions
const PAGE_OBJECT_TYPE_SPECIFIC_FEEDBACK
type for specific feedback page objects
getTaxItems(string $parentType, int $parentObjId, int $taxId, int $taxNode)