ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilTestQuestionBrowserTableGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
27 
33 {
35  private const REPOSITORY_ROOT_NODE_ID = 1;
36 
37  public const CONTEXT_PARAMETER = 'question_browse_context';
38  public const CONTEXT_PAGE_VIEW = 'contextPageView';
39  public const CONTEXT_LIST_VIEW = 'contextListView';
40 
41  public const MODE_PARAMETER = 'question_browse_mode';
42  public const MODE_BROWSE_POOLS = 'modeBrowsePools';
43  public const MODE_BROWSE_TESTS = 'modeBrowseTests';
44 
45  public const CMD_BROWSE_QUESTIONS = 'browseQuestions';
46  public const CMD_APPLY_FILTER = 'applyFilter';
47  public const CMD_RESET_FILTER = 'resetFilter';
48  public const CMD_INSERT_QUESTIONS = 'insertQuestions';
49 
50  private bool $writeAccess = false;
51 
53  private array $filter = [];
54 
55  public function __construct(
56  private ilTabsGUI $tabs,
57  private ilTree $tree,
58  private ilDBInterface $db,
59  private ilLogger $log,
60  private ilComponentRepository $component_repository,
61  private ilObjTest $test_obj,
62  private ilAccessHandler $access,
63  private GlobalHttpState $http_state,
64  private Refinery $refinery,
65  private UIFactory $ui_factory,
66  private UIRenderer $ui_renderer,
67  private InternalRequestService $testrequest,
68  private ILIAS\TestQuestionPool\QuestionInfoService $questioninfo
69  ) {
70  $this->setId('qpl_brows_tabl_' . $this->test_obj->getId());
71 
72  parent::__construct($this, self::CMD_BROWSE_QUESTIONS);
73  $this->setFilterCommand(self::CMD_APPLY_FILTER);
74  $this->setResetCommand(self::CMD_RESET_FILTER);
75 
76  $this->setFormName('questionbrowser');
77  $this->setStyle('table', 'fullwidth');
78  $this->addColumn('', '', '1%', true);
79  $this->addColumn($this->lng->txt("tst_question_title"), 'title', '');
80  $this->addColumn($this->lng->txt("description"), 'description', '');
81  $this->addColumn($this->lng->txt("tst_question_type"), 'ttype', '');
82  $this->addColumn($this->lng->txt("author"), 'author', '');
83  $this->addColumn($this->lng->txt('qst_lifecycle'), 'lifecycle', '');
84  $this->addColumn($this->lng->txt("create_date"), 'created', '');
85  $this->addColumn(
86  $this->lng->txt("last_update"),
87  'tstamp',
88  ''
89  ); // name of col is proper "updated" but in data array the key is "tstamp"
90  $this->addColumn($this->getParentObjectLabel(), 'parent_title', '');
91  $this->setSelectAllCheckbox('q_id');
92  $this->setRowTemplate("tpl.il_as_tst_question_browser_row.html", "Modules/Test");
93 
94  $this->setFormAction($this->ctrl->getFormAction($this->getParentObject(), $this->getParentCmd()));
95  $this->setDefaultOrderField("title");
96  $this->setDefaultOrderDirection("asc");
97 
98  $this->enable('sort');
99  $this->enable('select_all');
100  $this->initFilter();
101  $this->setDisableFilterHiding(true);
102  }
103 
104  public function setWriteAccess(bool $value): void
105  {
106  $this->writeAccess = $value;
107  }
108 
109  public function hasWriteAccess(): bool
110  {
111  return $this->writeAccess;
112  }
113 
114  public function init(): void
115  {
116  if ($this->hasWriteAccess()) {
117  $this->addMultiCommand(self::CMD_INSERT_QUESTIONS, $this->lng->txt('insert'));
118  }
119  }
120 
121  public function executeCommand(): bool
122  {
123  $this->handleParameters();
124  $this->handleTabs();
125 
126  switch (strtolower((string) $this->ctrl->getNextClass($this))) {
127  case strtolower(__CLASS__):
128  case '':
129 
130  $cmd = $this->ctrl->getCmd() . 'Cmd';
131  return $this->$cmd();
132 
133  default:
134 
135  $this->ctrl->setReturn($this, self::CMD_BROWSE_QUESTIONS);
136  return parent::executeCommand();
137  }
138  }
139 
140  private function browseQuestionsCmd(): bool
141  {
142  $this->setData($this->getQuestionsData());
143 
144  $this->main_tpl->setContent($this->ctrl->getHTML($this));
145  return true;
146  }
147 
148  private function applyFilterCmd(): void
149  {
150  $this->writeFilterToSession();
151  $this->ctrl->redirect($this, self::CMD_BROWSE_QUESTIONS);
152  }
153 
154  private function resetFilterCmd(): void
155  {
156  $this->resetFilter();
157  $this->ctrl->redirect($this, self::CMD_BROWSE_QUESTIONS);
158  }
159 
160  private function insertQuestionsCmd(): void
161  {
162  $selected_array = [];
163  if ($this->http_state->wrapper()->post()->has('q_id')) {
164  $selected_array = $this->http_state->wrapper()->post()->retrieve(
165  'q_id',
166  $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int())
167  );
168  }
169 
170  if ($selected_array === []) {
171  $this->main_tpl->setOnScreenMessage('info', $this->lng->txt("tst_insert_missing_question"), true);
172  $this->ctrl->redirect($this, self::CMD_BROWSE_QUESTIONS);
173  }
174 
175  $testQuestionSetConfig = $this->buildTestQuestionSetConfig();
176 
177  $manscoring = false;
178 
179  foreach ($selected_array as $key => $value) {
180  $last_question_id = $this->test_obj->insertQuestion($testQuestionSetConfig, $value);
181 
182  if (!$manscoring) {
183  $manscoring |= assQuestion::_needsManualScoring($value);
184  }
185  }
186 
187  $this->test_obj->saveCompleteStatus($testQuestionSetConfig);
188 
189  if ($manscoring) {
190  $this->main_tpl->setOnScreenMessage('info', $this->lng->txt("manscoring_hint"), true);
191  } else {
192  $this->main_tpl->setOnScreenMessage('success', $this->lng->txt("tst_questions_inserted"), true);
193  }
194 
195  $this->ctrl->redirectByClass($this->getBackTargetCmdClass(), $this->getBackTargetCommand());
196  }
197 
198  private function handleParameters(): void
199  {
200  if ($this->testrequest->isset(self::CONTEXT_PARAMETER)) {
201  $this->ctrl->setParameterByClass(
202  self::class,
203  self::CONTEXT_PARAMETER,
204  $this->testrequest->raw(self::CONTEXT_PARAMETER)
205  );
206  $this->addHiddenInput(self::CONTEXT_PARAMETER, $this->testrequest->raw(self::CONTEXT_PARAMETER));
207  }
208 
209  if ($this->testrequest->isset(self::MODE_PARAMETER)) {
210  $this->ctrl->setParameterByClass(
211  self::class,
212  self::MODE_PARAMETER,
213  $this->testrequest->raw(self::MODE_PARAMETER)
214  );
215  $this->addHiddenInput(self::MODE_PARAMETER, $this->testrequest->raw(self::MODE_PARAMETER));
216  }
217  }
218 
222  private function fetchContextParameter()
223  {
224  if ($this->testrequest->isset(self::CONTEXT_PARAMETER)) {
225  return $this->testrequest->raw(self::CONTEXT_PARAMETER);
226  }
227 
228  return null;
229  }
230 
234  private function fetchModeParameter()
235  {
236  if ($this->testrequest->isset(self::MODE_PARAMETER)) {
237  return $this->testrequest->raw(self::MODE_PARAMETER);
238  }
239 
240  return null;
241  }
242 
243  private function handleTabs(): void
244  {
245  $this->tabs->clearTargets();
246  $this->tabs->clearSubTabs();
247 
248  $this->tabs->setBackTarget(
249  $this->getBackTargetLabel(),
250  $this->getBackTargetUrl()
251  );
252 
253  $this->tabs->addTab(
254  'browseQuestions',
256  $this->getBrowseQuestionsTabUrl()
257  );
258  $this->tabs->activateTab('browseQuestions');
259  }
260 
261  private function getBackTargetLabel(): string
262  {
263  return $this->lng->txt('backtocallingtest');
264  }
265 
266  private function getBackTargetUrl(): string
267  {
268  return $this->ctrl->getLinkTargetByClass(
269  $this->getBackTargetCmdClass(),
270  $this->getBackTargetCommand()
271  );
272  }
273 
274  private function getBackTargetCmdClass(): string
275  {
276  switch ($this->fetchContextParameter()) {
277  case self::CONTEXT_PAGE_VIEW:
278 
279  return 'ilTestExpressPageObjectGUI';
280  case self::CONTEXT_LIST_VIEW:
281  default:
282  return 'ilObjTestGUI';
283  }
284  }
285 
286  private function getBackTargetCommand(): string
287  {
288  switch ($this->fetchContextParameter()) {
289  case self::CONTEXT_LIST_VIEW:
290 
291  return 'questions';
292 
293  case self::CONTEXT_PAGE_VIEW:
294 
295  return 'showPage';
296  }
297 
298  return '';
299  }
300 
301  private function getBrowseQuestionsTabLabel(): string
302  {
303  switch ($this->fetchModeParameter()) {
304  case self::MODE_BROWSE_POOLS:
305 
306  return $this->lng->txt('tst_browse_for_qpl_questions');
307 
308  case self::MODE_BROWSE_TESTS:
309 
310  return $this->lng->txt('tst_browse_for_tst_questions');
311  }
312 
313  return '';
314  }
315 
316  private function getBrowseQuestionsTabUrl(): string
317  {
318  return $this->ctrl->getLinkTarget($this, self::CMD_BROWSE_QUESTIONS);
319  }
320 
321  public function initFilter(): void
322  {
323  $ti = new ilTextInputGUI($this->lng->txt("tst_qbt_filter_question_title"), "title");
324  $ti->setMaxLength(64);
325  $ti->setSize(20);
326  $ti->setValidationRegexp('/(^[^%]+$)|(^$)/is');
327  $this->addFilterItem($ti);
328  $ti->readFromSession();
329  $this->filter["title"] = $ti->getValue();
330 
331  $ti = new ilTextInputGUI($this->lng->txt("description"), "description");
332  $ti->setMaxLength(64);
333  $ti->setSize(20);
334  $ti->setValidationRegexp('/(^[^%]+$)|(^$)/is');
335  $this->addFilterItem($ti);
336  $ti->readFromSession();
337  $this->filter["description"] = $ti->getValue();
338 
339  $ti = new ilTextInputGUI($this->lng->txt("author"), "author");
340  $ti->setMaxLength(64);
341  $ti->setSize(20);
342  $this->addFilterItem($ti);
343  $ti->setValidationRegexp('/(^[^%]+$)|(^$)/is');
344  $ti->readFromSession();
345  $this->filter["author"] = $ti->getValue();
346 
347  $lifecycleOptions = array_merge(
348  ['' => $this->lng->txt('qst_lifecycle_filter_all')],
349  ilAssQuestionLifecycle::getDraftInstance()->getSelectOptions($this->lng)
350  );
351  $lifecycleInp = new ilSelectInputGUI($this->lng->txt('qst_lifecycle'), 'lifecycle');
352  $lifecycleInp->setOptions($lifecycleOptions);
353  $this->addFilterItem($lifecycleInp);
354  $lifecycleInp->readFromSession();
355  $this->filter['lifecycle'] = $lifecycleInp->getValue();
356 
358  $options = [];
359  $options[""] = $this->lng->txt('filter_all_question_types');
360  foreach ($types as $translation => $row) {
361  $options[$row['type_tag']] = $translation;
362  }
363 
364  $si = new ilSelectInputGUI($this->lng->txt("question_type"), "type");
365  $si->setOptions($options);
366  $this->addFilterItem($si);
367  $si->readFromSession();
368  $this->filter["type"] = $si->getValue();
369 
370  $ti = new ilTextInputGUI($this->getParentObjectLabel(), 'parent_title');
371  $ti->setMaxLength(64);
372  $ti->setSize(20);
373  $ti->setValidationRegexp('/(^[^%]+$)|(^$)/is');
374  $this->addFilterItem($ti);
375  $ti->readFromSession();
376  $this->filter['parent_title'] = $ti->getValue();
377 
378  $ri = new ilRepositorySelectorInputGUI($this->lng->txt('repository'), 'repository_root_node');
379  $ri->setHeaderMessage($this->lng->txt('question_browse_area_info'));
380  if ($this->fetchModeParameter() === self::MODE_BROWSE_TESTS) {
381  $ri->setClickableTypes(['tst']);
382  } else {
383  $ri->setClickableTypes(['qpl']);
384  }
385  $this->addFilterItem($ri);
386  $ri->readFromSession();
387 
388  if (!$this->tree->isDeleted($ri->getValue())
389  && $this->tree->isInTree($ri->getValue())) {
390  $this->filter['repository_root_node'] = $ri->getValue();
391  } else {
392  $ri->setValue(self::REPOSITORY_ROOT_NODE_ID);
393  }
394  }
395 
396  private function getParentObjectLabel(): string
397  {
398  switch ($this->fetchModeParameter()) {
399  case self::MODE_BROWSE_POOLS:
400 
401  return $this->lng->txt('qpl');
402 
403  case self::MODE_BROWSE_TESTS:
404 
405  return $this->lng->txt('tst');
406  }
407 
408  return '';
409  }
410 
411  protected function getTranslatedLifecycle(?string $lifecycle): string
412  {
413  try {
414  return ilAssQuestionLifecycle::getInstance($lifecycle)->getTranslation($this->lng);
416  return '';
417  }
418  }
419 
420  public function fillRow(array $a_set): void
421  {
422  $this->tpl->setVariable("QUESTION_ID", $a_set["question_id"]);
423  $this->tpl->setVariable("QUESTION_TITLE", $a_set["title"]);
424  $this->tpl->setVariable("QUESTION_COMMENT", $a_set["description"]);
425  $this->tpl->setVariable("QUESTION_TYPE", $this->questioninfo->getQuestionTypeName($a_set["question_id"]));
426  $this->tpl->setVariable("QUESTION_AUTHOR", $a_set["author"]);
427  $this->tpl->setVariable("QUESTION_LIFECYCLE", $this->getTranslatedLifecycle($a_set['lifecycle']));
428  $this->tpl->setVariable(
429  "QUESTION_CREATED",
430  ilDatePresentation::formatDate(new ilDate($a_set['created'], IL_CAL_UNIX))
431  );
432  $this->tpl->setVariable(
433  "QUESTION_UPDATED",
434  ilDatePresentation::formatDate(new ilDate($a_set["tstamp"], IL_CAL_UNIX))
435  );
436  $this->tpl->setVariable(
437  "QUESTION_POOL_OR_TEST_TITLE",
439  (int) $a_set["obj_fi"],
440  $a_set["parent_title"]
441  )
442  );
443  }
444 
445  private function buildPossiblyLinkedQuestonPoolOrTestTitle(int $obj_id, string $parent_title): string
446  {
447  switch ($this->fetchModeParameter()) {
448  case self::MODE_BROWSE_POOLS:
449  return $this->buildPossiblyLinkedQuestonPoolTitle(
450  $this->ctrl,
451  $this->access,
452  $this->lng,
453  $this->ui_factory,
454  $this->ui_renderer,
455  $obj_id,
456  $parent_title
457  );
458 
459  case self::MODE_BROWSE_TESTS:
460  return $this->buildPossiblyLinkedTestTitle(
461  $this->ctrl,
462  $this->access,
463  $this->lng,
464  $this->ui_factory,
465  $this->ui_renderer,
466  $obj_id,
467  $parent_title
468  );
469  }
470 
471  return '';
472  }
473 
475  {
476  $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory(
477  $this->tree,
478  $this->db,
479  $this->lng,
480  $this->log,
481  $this->component_repository,
482  $this->test_obj,
483  $this->questioninfo
484  );
485 
486  return $testQuestionSetConfigFactory->getQuestionSetConfig();
487  }
488 
489  private function getQuestionsData(): array
490  {
491  $questionList = new ilAssQuestionList($this->db, $this->lng, $this->refinery, $this->component_repository);
492 
493  $questionList->setQuestionInstanceTypeFilter($this->getQuestionInstanceTypeFilter());
494  $questionList->setExcludeQuestionIdsFilter($this->test_obj->getExistingQuestions());
495 
496  $repositoryRootNode = self::REPOSITORY_ROOT_NODE_ID;
497 
498  foreach ($this->getFilterItems() as $item) {
499  if (!in_array($item->getValue(), [false, null, ''], true)) {
500  switch ($item->getPostVar()) {
501  case 'title':
502  case 'description':
503  case 'author':
504  case 'lifecycle':
505  case 'type':
506  case 'parent_title':
507  $questionList->addFieldFilter($item->getPostVar(), $item->getValue());
508  break;
509 
510  case 'repository_root_node':
511  $repositoryRootNode = (int) $item->getValue();
512  }
513  }
514  }
515  if ($repositoryRootNode < 1) {
516  $repositoryRootNode = self::REPOSITORY_ROOT_NODE_ID;
517  }
518 
519  $parentObjectIds = $this->getQuestionParentObjIds($repositoryRootNode);
520 
521  if (!count($parentObjectIds)) {
522  return [];
523  }
524 
525  $questionList->setParentObjIdsFilter($parentObjectIds);
526  $questionList->setParentObjectType($this->getQuestionParentObjectType());
527 
528  $questionList->load();
529 
530  return $questionList->getQuestionDataArray();
531  }
532 
533  private function getQuestionInstanceTypeFilter(): string
534  {
535  if ($this->fetchModeParameter() === self::MODE_BROWSE_TESTS) {
537  }
538 
540  }
541 
546  private function getQuestionParentObjIds(int $repositoryRootNode): array
547  {
548  $parents = $this->tree->getSubTree(
549  $this->tree->getNodeData($repositoryRootNode),
550  true,
551  [$this->getQuestionParentObjectType()]
552  );
553 
554  $parentIds = [];
555 
556  foreach ($parents as $nodeData) {
557  if ((int) $nodeData['obj_id'] === $this->test_obj->getId()) {
558  continue;
559  }
560 
561  $parentIds[$nodeData['obj_id']] = $nodeData['obj_id'];
562  }
563 
564  $parentIds = array_map('intval', array_values($parentIds));
565 
566  if ($this->fetchModeParameter() === self::MODE_BROWSE_POOLS) {
567  $available_pools = array_map('intval', array_keys(ilObjQuestionPool::_getAvailableQuestionpools(true)));
568  return array_intersect($parentIds, $available_pools);
569  } elseif ($this->fetchModeParameter() === self::MODE_BROWSE_TESTS) {
570  return array_filter($parentIds, function ($obj_id): bool {
571  $refIds = ilObject::_getAllReferences($obj_id);
572  $refId = current($refIds);
573  return $this->access->checkAccess('write', '', $refId);
574  });
575  }
576 
577  // Return no parent ids if the user wants to hack...
578  return [];
579  }
580 
581  private function getQuestionParentObjectType(): string
582  {
583  if ($this->fetchModeParameter() === self::MODE_BROWSE_TESTS) {
584  return 'tst';
585  }
586 
587  return 'qpl';
588  }
589 }
setData(array $a_data)
Interface GlobalHttpState.
Readable part of repository interface to ilComponentDataDB.
enable(string $a_module_name)
This class represents a selection list property in a property form.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setFormAction(string $a_form_action, bool $a_multipart=false)
addFilterItem(ilTableFilterItem $a_input_item, bool $a_optional=false)
static _getAvailableQuestionpools( $use_object_id=false, $equal_points=false, $could_be_offline=false, $showPath=false, $with_questioncount=false, $permission='read', $usr_id='')
Returns the available question pools for the active user.
setResetCommand(string $a_val, string $a_caption="")
Class ChatMainBarProvider .
setSelectAllCheckbox(string $a_select_all_checkbox, bool $a_select_all_on_top=false)
static _getAllReferences(int $id)
get all reference ids for object ID
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false)
setDisableFilterHiding(bool $a_val=true)
$refId
Definition: xapitoken.php:58
setOptions(array $a_options)
const IL_CAL_UNIX
setFormName(string $a_name="")
setId(string $a_val)
buildPossiblyLinkedQuestonPoolOrTestTitle(int $obj_id, string $parent_title)
setStyle(string $a_element, string $a_style)
static _needsManualScoring(int $question_id)
__construct(VocabulariesInterface $vocabularies)
addHiddenInput(string $a_name, string $a_value)
setDefaultOrderField(string $a_defaultorderfield)
$log
Definition: result.php:33
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setRowTemplate(string $a_template, string $a_template_dir="")
Set row template.
setFilterCommand(string $a_val, string $a_caption="")
string $key
Consumer key/client ID value.
Definition: System.php:193
setDefaultOrderDirection(string $a_defaultorderdirection)
$lifecycle
addColumn(string $a_text, string $a_sort_field="", string $a_width="", bool $a_is_checkbox_action_column=false, string $a_class="", string $a_tooltip="", bool $a_tooltip_with_html=false)
filter(string $filter_id, $class_path, string $cmd, bool $activated=true, bool $expanded=true)
getFilterItems(bool $a_optionals=false)
__construct(private ilTabsGUI $tabs, private ilTree $tree, private ilDBInterface $db, private ilLogger $log, private ilComponentRepository $component_repository, private ilObjTest $test_obj, private ilAccessHandler $access, private GlobalHttpState $http_state, private Refinery $refinery, private UIFactory $ui_factory, private UIRenderer $ui_renderer, private InternalRequestService $testrequest, private ILIAS\TestQuestionPool\QuestionInfoService $questioninfo)
addMultiCommand(string $a_cmd, string $a_text)
static _getQuestionTypes($all_tags=false, $fixOrder=false, $withDeprecatedTypes=true)
Refinery Factory $refinery