82 private ?NotesService $notes_service =
null
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'];
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" :
'';
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";
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];
678 return $this->questions;
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.'
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)
getTotalRowCount(?array $filter_data, ?array $additional_parameters)
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)
array $availableTaxonomyIds
getTaxonomyFilterExpressions()
const QUESTION_INSTANCE_TYPE_ALL
handleFeedbackJoin(string $tableJoin)
setParentObjIdsFilter(array $parentObjIdsFilter)
getQuestionInstanceTypeFilterExpression()
const QUESTION_INSTANCE_TYPE_ORIGINALS
getDataArrayForQuestionId(int $questionId)
array $includeQuestionIdsFilter
const ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY
getParentObjFilterExpression()
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
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)
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...