84 private ?NotesService $notes_service =
null
92 $this->order_field = $order_state[
'order_field'];
93 $this->order_direction = $order_state[
'order_direction'];
128 $this->includeQuestionIdsFilter = $questionIdsFilter;
138 $this->fieldFilters[$fieldName] = $fieldValue;
143 $this->taxFilters[$taxId] = $taxNodes;
150 $this->taxFiltersExcludeAnyObjectsWithTaxonomies = $flag;
173 $this->join_obj_data = $a_val;
178 if ($this->parentObjId) {
179 return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, ilDBConstants::T_INTEGER)}";
182 if (!empty($this->parentObjIdsFilter)) {
193 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
194 switch ($fieldName) {
199 $expressions[] = $this->db->like(
"qpl_questions.$fieldName",
ilDBConstants::T_TEXT,
"%%$fieldValue%%");
202 $expressions[] =
"qpl_qst_type.type_tag = {$this->db->quote($fieldValue, ilDBConstants::T_TEXT)}";
205 if ($fieldValue !==
'' && !is_array($fieldValue)) {
206 $fieldValue = [$fieldValue];
211 if ($this->join_obj_data) {
223 $feedback_join = match ($this->fieldFilters[
'feedback'] ??
null) {
229 if (isset($feedback_join)) {
230 $SQL =
"$feedback_join JOIN qpl_fb_generic ON qpl_fb_generic.question_fi = qpl_questions.question_id ";
231 $tableJoin .= !str_contains($tableJoin, $SQL) ? $SQL :
'';
241 $taxonomy_title = $this->fieldFilters[
'taxonomy_title'] ??
'';
242 $taxonomy_node_title = $this->fieldFilters[
'taxonomy_node_title'] ??
'';
244 if ($taxonomy_title ===
'' && $taxonomy_node_title ===
'') {
248 $base =
'SELECT DISTINCT item_id FROM tax_node_assignment';
250 $like_taxonomy_title = $taxonomy_title !==
''
251 ?
"AND {$this->db->like('object_data.title', ilDBConstants::T_TEXT, "%$taxonomy_title%
", false)}"
253 $like_taxonomy_node_title = $taxonomy_node_title !==
''
254 ?
"AND {$this->db->like('tax_node.title', ilDBConstants::T_TEXT, "%$taxonomy_node_title%
", false)}"
257 $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)";
258 $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)";
260 $expressions[] =
"qpl_questions.question_id IN ($base $inner_join_object_data $inner_join_tax_node)";
267 if ($this->taxFiltersExcludeAnyObjectsWithTaxonomies) {
268 return [
'question_id NOT IN (SELECT DISTINCT item_id FROM tax_node_assignment)'];
272 foreach ($this->taxFilters as $tax_id => $tax_nodes) {
275 if ($tax_nodes === []) {
279 foreach ($tax_nodes as $tax_node) {
281 $this->taxParentTypes[$tax_id],
282 $this->taxParentIds[$tax_id],
288 $this->parentObjType,
294 $tax_items = array_merge($tax_items_by_tax_parent, $tax_items_by_parent);
295 foreach ($tax_items as $tax_item) {
296 $question_ids[$tax_item[
'item_id']] = $tax_item[
'item_id'];
317 $subNodes = $taxTree->getSubTreeIds($taxNode);
318 $subNodes[] = $taxNode;
320 return $taxNodeAssignment->getAssignmentsOfNode($subNodes);
325 return match ($this->questionInstanceTypeFilter) {
326 self::QUESTION_INSTANCE_TYPE_ORIGINALS =>
'qpl_questions.original_id IS NULL',
327 self::QUESTION_INSTANCE_TYPE_DUPLICATES =>
'qpl_questions.original_id IS NOT NULL',
336 if (!empty($this->includeQuestionIdsFilter)) {
337 $expressions[] = $this->db->in(
338 'qpl_questions.question_id',
339 $this->includeQuestionIdsFilter,
345 if (!empty($this->excludeQuestionIdsFilter)) {
347 'qpl_questions.question_id',
348 $this->excludeQuestionIdsFilter,
353 $expressions[] = $IN ===
' 1=2 ' ?
' 1=1 ' : $IN;
361 return match ($this->answerStatusFilter) {
362 self::ANSWER_STATUS_FILTER_ALL_NON_CORRECT => [
'
363 (tst_test_result.question_fi IS NULL OR tst_test_result.points < qpl_questions.points)
365 self::ANSWER_STATUS_FILTER_NON_ANSWERED_ONLY => [
'tst_test_result.question_fi IS NULL'],
366 self::ANSWER_STATUS_FILTER_WRONG_ANSWERED_ONLY => [
367 'tst_test_result.question_fi IS NOT NULL',
368 'tst_test_result.points < qpl_questions.points'
376 $tableJoin =
"INNER JOIN qpl_qst_type ON qpl_qst_type.question_type_id = qpl_questions.question_type_fi ";
378 if ($this->join_obj_data) {
380 INNER JOIN object_data ON object_data.obj_id = qpl_questions.obj_fi
381 INNER JOIN object_reference ON object_reference.obj_id = object_data.obj_id
386 $this->parentObjType ===
'tst'
387 && $this->questionInstanceTypeFilter === self::QUESTION_INSTANCE_TYPE_ALL
389 $tableJoin .=
'INNER JOIN tst_test_question tstquest ON tstquest.question_fi = qpl_questions.question_id';
394 if ($this->answerStatusActiveId) {
396 LEFT JOIN tst_test_result
397 ON tst_test_result.question_fi = qpl_questions.question_id
398 AND tst_test_result.active_fi = {$this->db->quote($this->answerStatusActiveId, ilDBConstants::T_INTEGER)}
417 $conditions = array_merge(
425 $conditions = implode(
' AND ', $conditions);
426 return $conditions !==
'' ?
"AND $conditions" :
'';
433 'qpl_qst_type.type_tag AS question_type',
434 'qpl_qst_type.plugin',
435 'qpl_qst_type.plugin_name',
436 'qpl_questions.points max_points'
439 if ($this->join_obj_data) {
440 $select_fields[] =
'object_data.title parent_title, object_data.type parent_type, object_reference.ref_id parent_ref_id';
443 if ($this->answerStatusActiveId) {
444 $select_fields[] =
'tst_test_result.points reached_points';
445 $select_fields[] =
"CASE
446 WHEN tst_test_result.points IS NULL THEN '" . self::QUESTION_ANSWER_STATUS_NON_ANSWERED .
"'
447 WHEN tst_test_result.points < qpl_questions.points THEN '" . self::QUESTION_ANSWER_STATUS_WRONG_ANSWERED .
"'
448 ELSE '" . self::QUESTION_ANSWER_STATUS_CORRECT_ANSWERED .
"'
449 END question_answer_status
457 $select_fields = implode(
', ', $select_fields);
458 return "SELECT DISTINCT $select_fields";
464 $tables = [
'qpl_fb_generic',
'qpl_fb_specific'];
466 foreach ($tables as $table) {
467 $subquery =
"SELECT 1 FROM $table WHERE $table.question_fi = qpl_questions.question_id AND $table.feedback <> ''";
468 $cases[] =
"WHEN EXISTS ($subquery) THEN TRUE";
471 $page_object_table =
'page_object';
472 foreach ($tables as $table) {
474 "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",
478 $cases[] =
"WHEN EXISTS ($subquery) THEN TRUE";
481 $feedback_case_subquery = implode(
' ', $cases);
482 return "CASE $feedback_case_subquery ELSE FALSE END AS feedback";
487 $tax_node_assignment_table =
'tax_node_assignment';
488 $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'";
489 return "CASE WHEN EXISTS ($tax_subquery) THEN TRUE ELSE FALSE END AS taxonomies";
494 return "{$this->getSelectFieldsExpression()} FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0";
501 foreach ($this->fieldFilters as $fieldName => $fieldValue) {
502 if ($fieldName ===
'feedback') {
503 $fieldValue = strtoupper($fieldValue);
504 if (in_array($fieldValue, [
'TRUE',
'FALSE'],
true)) {
505 $expressions[] =
"feedback IS $fieldValue";
512 $having = implode(
' AND ', $expressions);
513 return $having !==
'' ?
"HAVING $having" :
'';
518 return $this->order_field ===
null || $this->order_direction ===
null || $this->order_field ===
'question_type'
520 :
" ORDER BY `$this->order_field` $this->order_direction";
526 if (
$order instanceof
Order && $this->order_field ===
'question_type') {
538 return " LIMIT $limit OFFSET $offset";
544 return [
'order_field' =>
null,
'order_direction' =>
null];
549 static fn(
string $index,
string $key,
string $value): array => [$key, $value]
562 return implode(PHP_EOL, array_filter([
575 $tags_trafo = $this->
refinery->encode()->htmlSpecialCharsAsEntities();
578 while ($row = $this->db->fetchAssoc(
$res)) {
585 $row[
'title'] = $tags_trafo->transform($row[
'title'] ??
' ');
586 $row[
'description'] = $tags_trafo->transform($row[
'description'] ??
'');
587 $row[
'author'] = $tags_trafo->transform($row[
'author']);
589 $row[
'question_type'] = $this->
lng->txt($row[
'question_type']);
590 $row[
'feedback'] = $row[
'feedback'] === 1;
594 $this->filter_comments === self::QUESTION_COMMENTED_ONLY && $row[
'comments'] === 0
595 || $this->filter_comments === self::QUESTION_COMMENTED_EXCLUDED && $row[
'comments'] > 0
600 $this->questions[$row[
'question_id']] = $row;
603 if ($this->order_field ===
'question_type') {
610 if ($this->order_field ===
'question_type') {
611 usort(
$questions, fn(array
$a, array
$b):
int => strcmp(
$a[$this->order_field],
$b[$this->order_field]));
618 return $this->range instanceof
Range
619 ? array_slice(
$questions, $this->range->getStart(), $this->range->getLength())
624 mixed $additional_viewcontrol_data,
626 mixed $additional_parameters
631 $query =
"SELECT $count FROM qpl_questions {$this->getTableJoinExpression()} WHERE qpl_questions.tstamp > 0 {$this->getConditionalFilterExpression()}";
633 return (
int) ($this->db->query($query)->fetch()[$count] ?? 0);
638 if ($this->notes_service ===
null) {
641 $notes_context = $this->notes_service->data()->context(
642 $this->getParentObjId(),
646 return $this->notes_service->domain()->getNrOfCommentsForContext($notes_context);
651 $this->filter_comments = $commented;
658 $tax_assignment_data = [];
659 foreach ($this->availableTaxonomyIds as $tax_id) {
663 $assignments = $tax_assignment->getAssignmentsOfItem($question_id);
665 foreach ($assignments as $ass_data) {
666 if (!isset($tax_assignment_data[$ass_data[
'tax_id']])) {
667 $tax_assignment_data[$ass_data[
'tax_id']] = [];
670 $ass_data[
'node_lft'] = $tax_tree->getNodeData($ass_data[
'node_id']);
672 $tax_assignment_data[$ass_data[
'tax_id']][$ass_data[
'node_id']] = $ass_data;
676 return $tax_assignment_data;
681 if (!isset($questionData[
'plugin'])) {
685 if (!$questionData[
'plugin']) {
690 !isset($questionData[
'plugin_name'])
691 || !$this->component_repository->getComponentByTypeAndName(
694 )->getPluginSlotById(
'qst')->hasPluginName($questionData[
'plugin_name'])
699 return $this->component_repository
701 ->getPluginSlotById(
'qst')
702 ->getPluginByName($questionData[
'plugin_name'])
708 return $this->questions[$questionId];
713 return $this->questions;
718 return isset($this->questions[$questionId]);
730 public function getTitle(
string $a_comp_id,
string $a_item_type,
int $a_item_id): string
732 if ($a_comp_id !==
'qpl' || $a_item_type !==
'quest' || !$a_item_id) {
736 return $this->questions[$a_item_id][
'title'] ??
'';
741 if ($this->answerStatusFilter !==
'' && !$this->answerStatusActiveId) {
743 '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
handleFeedbackJoin(string $tableJoin)
postLimit(array $questions)
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)
postOrder(array $questions)
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)
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