ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilTestScoringGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 require_once "./Modules/Test/classes/inc.AssessmentConstants.php";
22 
31 {
32  public const PART_FILTER_ALL_USERS = 3; // default
33  public const PART_FILTER_MANSCORING_DONE = 4;
34  public const PART_FILTER_MANSCORING_NONE = 5;
35  //const PART_FILTER_MANSCORING_PENDING = 6;
36 
40  protected $testAccess;
41 
50  public function __construct(ilObjTest $a_object)
51  {
52  parent::__construct($a_object);
53  }
54 
58  public function getTestAccess(): ilTestAccess
59  {
60  return $this->testAccess;
61  }
62 
66  public function setTestAccess($testAccess)
67  {
68  $this->testAccess = $testAccess;
69  }
70 
74  protected function buildSubTabs($active_sub_tab = 'man_scoring_by_qst')
75  {
76  $this->tabs->addSubTab('man_scoring_by_qst', $this->lng->txt('tst_man_scoring_by_qst'), $this->ctrl->getLinkTargetByClass('ilTestScoringByQuestionsGUI', 'showManScoringByQuestionParticipantsTable'));
77  $this->tabs->addSubTab('man_scoring', $this->lng->txt('tst_man_scoring_by_part'), $this->ctrl->getLinkTargetByClass('ilTestScoringGUI', 'showManScoringParticipantsTable'));
78  $this->tabs->setSubTabActive($active_sub_tab);
79  }
80 
81  private function fetchActiveIdParameter(): int
82  {
83  if (!$this->testrequest->isset('active_id') || $this->testrequest->int('active_id') === 0) {
84  $this->tpl->setOnScreenMessage('failure', 'no active id given!', true);
85  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
86  }
87 
88  return $this->testrequest->int('active_id');
89  }
90 
91  private function fetchPassParameter(int $active_id): int
92  {
93  $max_pass = $this->object->_getMaxPass($active_id);
94 
95 
96  if ($this->testrequest->isset('pass')) {
97  $pass_from_request = $this->testrequest->int('pass');
98  if ($pass_from_request >= 0
99  && $pass_from_request <= $max_pass
100  ) {
101  return $pass_from_request;
102  }
103  }
104 
105  if ($this->object->getPassScoring() == SCORE_LAST_PASS) {
106  return $max_pass;
107  }
108 
109  return $this->object->_getResultPass($active_id);
110  }
111 
115  public function executeCommand()
116  {
117  if (!$this->getTestAccess()->checkScoreParticipantsAccess()) {
118  ilObjTestGUI::accessViolationRedirect();
119  }
120 
122  // allow only if at least one question type is marked for manual scoring
123  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("manscoring_not_allowed"), true);
124  $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen");
125  }
126 
127  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_MANUAL_SCORING);
128  $this->buildSubTabs($this->getActiveSubTabId());
129 
130  $nextClass = $this->ctrl->getNextClass($this);
131  $command = $this->ctrl->getCmd($this->getDefaultCommand());
132 
133  switch ($nextClass) {
134  default:
135  $this->$command();
136  break;
137  }
138  }
139 
140  protected function getDefaultCommand(): string
141  {
142  return 'manscoring';
143  }
144 
145  protected function getActiveSubTabId(): string
146  {
147  return 'man_scoring';
148  }
149 
150  private function showManScoringParticipantsTable(): void
151  {
152  $table = $this->buildManScoringParticipantsTable(true);
153  $this->tpl->setContent($table->getHTML());
154  }
155 
156  private function applyManScoringParticipantsFilter(): void
157  {
158  $table = $this->buildManScoringParticipantsTable(false);
159  $table->resetOffset();
160  $table->writeFilterToSession();
161 
163  }
164 
165  private function resetManScoringParticipantsFilter(): void
166  {
167  $table = $this->buildManScoringParticipantsTable(false);
168  $table->resetOffset();
169  $table->resetFilter();
170 
172  }
173 
174  private function showManScoringParticipantScreen(ilPropertyFormGUI $form = null): void
175  {
176  $active_id = $this->fetchActiveIdParameter();
177 
178  if (!$this->getTestAccess()->checkScoreParticipantsAccessForActiveId($active_id)) {
179  ilObjTestGUI::accessViolationRedirect();
180  }
181 
182  $pass = $this->fetchPassParameter($active_id);
183 
184  $content_html = '';
185 
186  $table = new ilTestPassManualScoringOverviewTableGUI($this, 'showManScoringParticipantScreen');
187 
188  $user_id = $this->object->_getUserIdFromActiveId($active_id);
189  $user_fullname = $this->object->userLookupFullName($user_id, false, true);
190  $table_title = sprintf($this->lng->txt('tst_pass_overview_for_participant'), $user_fullname);
191  $table->setTitle($table_title);
192 
193  $passOverviewData = $this->service->getPassOverviewData($active_id);
194  $table->setData($passOverviewData['passes']);
195 
196  $content_html .= $table->getHTML() . '<br />';
197 
198  if ($form === null) {
199  $question_gui_list = $this->service->getManScoringQuestionGuiList($active_id, $pass);
200  $form = $this->buildManScoringParticipantForm($question_gui_list, $active_id, $pass, true);
201  }
202 
203  $content_html .= $form->getHTML();
204 
205  $this->tpl->setContent($content_html);
206  }
207 
208  private function saveManScoringParticipantScreen(bool $redirect = true): bool
209  {
210  $active_id = $this->fetchActiveIdParameter();
211 
212  if (!$this->getTestAccess()->checkScoreParticipantsAccessForActiveId($active_id)) {
213  ilObjTestGUI::accessViolationRedirect();
214  }
215 
216  $pass = $this->fetchPassParameter($active_id);
217 
218  $questionGuiList = $this->service->getManScoringQuestionGuiList($active_id, $pass);
219  $form = $this->buildManScoringParticipantForm($questionGuiList, $active_id, $pass, false);
220 
221  $form->setValuesByPost();
222 
223  if (!$form->checkInput()) {
224  $this->tpl->setOnScreenMessage('failure', sprintf($this->lng->txt('tst_save_manscoring_failed'), $pass + 1));
225  $this->showManScoringParticipantScreen($form);
226  return false;
227  }
228 
229  $maxPointsByQuestionId = [];
230  $maxPointsExceeded = false;
231  foreach ($questionGuiList as $questionId => $questionGui) {
232  $reachedPoints = $form->getItemByPostVar("question__{$questionId}__points")->getValue();
233  $maxPoints = $this->questioninfo->getMaximumPoints($questionId);
234 
235  if ($reachedPoints > $maxPoints) {
236  $maxPointsExceeded = true;
237 
238  $form->getItemByPostVar("question__{$questionId}__points")->setAlert(sprintf(
239  $this->lng->txt('tst_manscoring_maxpoints_exceeded_input_alert'),
240  $maxPoints
241  ));
242  }
243 
244  $maxPointsByQuestionId[$questionId] = $maxPoints;
245  }
246 
247  if ($maxPointsExceeded) {
248  $this->tpl->setOnScreenMessage('failure', sprintf($this->lng->txt('tst_save_manscoring_failed'), $pass + 1));
249  $this->showManScoringParticipantScreen($form);
250  return false;
251  }
252 
253  foreach ($questionGuiList as $questionId => $questionGui) {
254  $old_points = assQuestion::_getReachedPoints($active_id, $questionId, $pass);
255  $reached_points = $this->refinery->byTrying([
256  $this->refinery->kindlyTo()->float(),
257  $this->refinery->always($old_points)
258  ])->transform($form->getItemByPostVar("question__{$questionId}__points")?->getValue());
259 
260  $finalized = (bool) $form->getItemByPostVar("{$questionId}__evaluated")?->getChecked();
261  // fix #35543: save manual points only if they differ from the existing points
262  // this prevents a question being set to "answered" if only feedback is entered
263  if ($reached_points !== $old_points) {
265  $active_id,
266  $questionId,
267  $reached_points,
268  $maxPointsByQuestionId[$questionId],
269  $pass,
270  true,
271  $this->object->areObligationsEnabled(),
272  $this->getTestAccess()->getTestId()
273  );
274  }
275 
276  $feedback = ilUtil::stripSlashes(
277  (string) $form->getItemByPostVar("question__{$questionId}__feedback")->getValue(),
278  false,
280  );
281 
282  $this->object->saveManualFeedback($active_id, (int) $questionId, $pass, $feedback, $finalized, true);
283 
284  $notificationData[$questionId] = [
285  'points' => $reached_points, 'feedback' => $feedback
286  ];
287  }
288 
290  $this->object->getId(),
292  );
293 
294  $manScoringDone = $form->getItemByPostVar("manscoring_done")->getChecked();
295  ilTestService::setManScoringDone($active_id, $manScoringDone);
296 
297  $manScoringNotify = $form->getItemByPostVar("manscoring_notify")->getChecked();
298  if ($manScoringNotify) {
299  $notification = new ilTestManScoringParticipantNotification(
300  $this->object->_getUserIdFromActiveId($active_id),
301  $this->object->getRefId()
302  );
303 
304  $notification->setAdditionalInformation(array(
305  'test_title' => $this->object->getTitle(),
306  'test_pass' => $pass + 1,
307  'questions_gui_list' => $questionGuiList,
308  'questions_scoring_data' => $notificationData
309  ));
310 
311  $notification->send();
312  }
313 
314  $scorer = new ilTestScoring($this->object, $this->db);
315  $scorer->setPreserveManualScores(true);
316  $scorer->recalculateSolution($active_id, $pass);
317 
318  if ($this->object->getAnonymity() == 0) {
320  $name_real_or_anon = $user_name['firstname'] . ' ' . $user_name['lastname'];
321  } else {
322  $name_real_or_anon = $this->lng->txt('anonymous');
323  }
324  $this->tpl->setOnScreenMessage('success', sprintf($this->lng->txt('tst_saved_manscoring_successfully'), $pass + 1, $name_real_or_anon), true);
325  if ($redirect == true) {
326  $this->ctrl->redirect($this, 'showManScoringParticipantScreen');
327  }
328  return true;
329  }
330 
331  private function saveNextManScoringParticipantScreen(): void
332  {
333  $table = $this->buildManScoringParticipantsTable(true);
334 
335  if ($this->saveManScoringParticipantScreen(false)) {
336  $participantData = $table->getInternalyOrderedDataValues();
337 
338  $nextIndex = null;
339  foreach ($participantData as $index => $participant) {
340  if ($participant['active_id'] == $this->testrequest->raw('active_id')) {
341  $nextIndex = $index + 1;
342  break;
343  }
344  }
345 
346  if ($nextIndex && isset($participantData[$nextIndex])) {
347  $this->ctrl->setParameter($this, 'active_id', $participantData[$nextIndex]['active_id']);
348  $this->ctrl->redirect($this, 'showManScoringParticipantScreen');
349  }
350 
351  $this->ctrl->redirectByClass("iltestscoringgui", "showManScoringParticipantsTable");
352  }
353  }
354 
355  private function saveReturnManScoringParticipantScreen(): void
356  {
357  if ($this->saveManScoringParticipantScreen(false)) {
358  $this->ctrl->redirectByClass("iltestscoringgui", "showManScoringParticipantsTable");
359  }
360  }
361 
363  array $questionGuiList,
364  int $active_id,
365  int $pass,
366  bool $initValues = false
367  ): ilPropertyFormGUI {
368  $this->ctrl->setParameter($this, 'active_id', $active_id);
369  $this->ctrl->setParameter($this, 'pass', $pass);
370 
371  $form = new ilPropertyFormGUI();
372  $form->setFormAction($this->ctrl->getFormAction($this));
373 
374  $form->setTitle(sprintf($this->lng->txt('manscoring_results_pass'), $pass + 1));
375  $form->setTableWidth('100%');
376 
377  $autosave_enabled = $this->object->getAutosave();
378  $show_solutions_enabled = $this->object->getShowSolutionFeedback();
379  foreach ($questionGuiList as $questionId => $questionGUI) {
380  $questionHeader = sprintf($this->lng->txt('tst_manscoring_question_section_header'), $questionGUI->object->getTitleForHTMLOutput());
381  $questionSolution = $questionGUI->getSolutionOutput($active_id, $pass, false, false, true, false, false, true);
382  $bestSolution = $questionGUI->object->getSuggestedSolutionOutput();
383 
384  $feedback = $this->object->getSingleManualFeedback($active_id, $questionId, $pass);
385 
386  $disabled = false;
387  if (isset($feedback['finalized_evaluation']) && $feedback['finalized_evaluation'] == 1) {
388  $disabled = true;
389  }
390 
391  $sect = new ilFormSectionHeaderGUI();
392  $sect->setTitle($questionHeader . ' [' . $this->lng->txt('question_id_short') . ': ' . $questionGUI->object->getId() . ']');
393  $form->addItem($sect);
394 
395  $cust = new ilCustomInputGUI($this->lng->txt('tst_manscoring_input_question_and_user_solution'));
396  $cust->setHtml($questionSolution);
397  $form->addItem($cust);
398 
399  if ($autosave_enabled) {
400  $aresult_output = $questionGUI->getAutoSavedSolutionOutput(
401  $active_id,
402  $pass,
403  false,
404  false,
405  true,
406  $show_solutions_enabled,
407  false,
408  true,
409  false
410  );
411  if ($aresult_output) {
412  $cust = new ilCustomInputGUI($this->lng->txt('autosavecontent'));
413  $cust->setHtml($aresult_output);
414  $form->addItem($cust);
415  }
416  }
417 
418  $number_input_gui = new \ilNumberInputGUI($this->lng->txt('tst_change_points_for_question'), "question__{$questionId}__points");
419  $number_input_gui->allowDecimals(true);
420  if ($initValues) {
421  $number_input_gui->setValue((string) \assQuestion::_getReachedPoints($active_id, $questionId, $pass));
422  }
423  if ($disabled) {
424  $number_input_gui->setDisabled($disabled);
425  }
426  $form->addItem($number_input_gui);
427 
428  $nonedit = new ilNonEditableValueGUI($this->lng->txt('tst_manscoring_input_max_points_for_question'), "question__{$questionId}__maxpoints");
429  if ($initValues) {
430  $nonedit->setValue($this->questioninfo->getMaximumPoints($questionId));
431  }
432  $form->addItem($nonedit);
433 
434  $area = new ilTextAreaInputGUI($this->lng->txt('set_manual_feedback'), "question__{$questionId}__feedback");
435  $area->setUseRTE(true);
436  if ($initValues) {
437  $area->setValue(ilObjTest::getSingleManualFeedback((int) $active_id, (int) $questionId, (int) $pass)['feedback'] ?? '');
438  }
439  if ($disabled) {
440  $area->setDisabled($disabled);
441  }
442  $form->addItem($area);
443 
444  $check = new ilCheckboxInputGUI($this->lng->txt('finalized_evaluation'), "{$questionId}__evaluated");
445  if ($disabled) {
446  $check->setChecked(true);
447  }
448  $form->addItem($check);
449 
450  if (strlen(trim($bestSolution))) {
451  $cust = new ilCustomInputGUI($this->lng->txt('tst_show_solution_suggested'));
452  $cust->setHtml($bestSolution);
453  $form->addItem($cust);
454  }
455  }
456 
457  $sect = new ilFormSectionHeaderGUI();
458  $sect->setTitle($this->lng->txt('tst_participant'));
459  $form->addItem($sect);
460 
461  $check = new ilCheckboxInputGUI($this->lng->txt('set_manscoring_done'), 'manscoring_done');
462  if ($initValues && ilTestService::isManScoringDone($active_id)) {
463  $check->setChecked(true);
464  }
465  $form->addItem($check);
466 
467  $check = new ilCheckboxInputGUI($this->lng->txt('tst_manscoring_user_notification'), 'manscoring_notify');
468  $form->addItem($check);
469 
470  $form->addCommandButton('saveManScoringParticipantScreen', $this->lng->txt('save'));
471  $form->addCommandButton('saveReturnManScoringParticipantScreen', $this->lng->txt('save_return'));
472  $form->addCommandButton('saveNextManScoringParticipantScreen', $this->lng->txt('save_and_next'));
473 
474  return $form;
475  }
476 
477  private function sendManScoringParticipantNotification(): void
478  {
479  }
480 
482  {
483  $table = new ilTestManScoringParticipantsTableGUI($this);
484 
485  if ($withData) {
486  $participantStatusFilterValue = $table->getFilterItemByPostVar('participant_status')->getValue();
487 
488  $participant_list = new ilTestParticipantList($this->object, $this->user, $this->lng, $this->db);
489 
490  $participant_list->initializeFromDbRows(
491  $this->object->getTestParticipantsForManualScoring($participantStatusFilterValue)
492  );
493 
494  $filtered_participant_list = $participant_list->getAccessFilteredList(
495  $this->participant_access_filter->getScoreParticipantsUserFilter($this->ref_id)
496  );
497 
498  $table->setData($filtered_participant_list->getParticipantsTableRows());
499  }
500 
501  return $table;
502  }
503 }
showManScoringParticipantScreen(ilPropertyFormGUI $form=null)
static _getParticipantId($active_id)
Get user id for active id.
const SCORE_LAST_PASS
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
buildSubTabs($active_sub_tab='man_scoring_by_qst')
static _lookupName(int $a_user_id)
lookup user name
__construct(ilObjTest $a_object)
ilTestScoringGUI constructor
static isManScoringDone(int $active_id)
buildManScoringParticipantForm(array $questionGuiList, int $active_id, int $pass, bool $initValues=false)
Scoring class for tests.
executeCommand()
execute command
static getSingleManualFeedback(int $active_id, int $question_id, int $pass)
static setManScoringDone(int $activeId, bool $manScoringDone)
__construct(VocabulariesInterface $vocabularies)
static _getReachedPoints(int $active_id, int $question_id, int $pass)
static _mananuallyScoreableQuestionTypesExists()
Returns the fact wether manually scoreable question types exist or not.
static _getUsedHTMLTagsAsString(string $a_module="")
Returns a string of all allowed HTML tags for text editing.
saveManScoringParticipantScreen(bool $redirect=true)
Class ilTestScoring.
ilTestParticipantData $participantData
This class represents a text area property in a property form.
$check
Definition: buildRTE.php:81
Service GUI class for tests.
static _setReachedPoints(int $active_id, int $question_id, float $points, float $maxpoints, int $pass, bool $manualscoring, bool $obligationsEnabled, ?int $test_id=null)
Sets the points, a learner has reached answering the question Additionally objective results are upda...
setAdditionalInformation(array $a_info)
buildManScoringParticipantsTable(bool $withData=false)
fetchPassParameter(int $active_id)
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)