ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilExerciseManagementGUI.php
Go to the documentation of this file.
1 <?php
2 
32 
41 {
42  public const VIEW_ASSIGNMENT = 1;
43  public const VIEW_PARTICIPANT = 2;
44  public const VIEW_GRADES = 3;
45 
46  public const FEEDBACK_ONLY_SUBMISSION = "submission_only";
47  public const FEEDBACK_FULL_SUBMISSION = "submission_feedback";
48 
49  public const GRADE_NOT_GRADED = "notgraded";
50  public const GRADE_PASSED = "passed";
51  public const GRADE_FAILED = "failed";
52  protected ZipAdapter $zip;
54  protected \ILIAS\Exercise\InternalDomainService $domain;
55  protected \ILIAS\Exercise\Notification\NotificationManager $notification;
58  protected \ILIAS\HTTP\Services $http;
59 
60  protected ilCtrl $ctrl;
61  protected ilTabsGUI $tabs_gui;
62  protected ilLanguage $lng;
64  protected Factory $ui_factory;
66  protected array $filter = [];
69  protected ?ilExAssignment $assignment = null;
71  protected ilLogger $log;
72  protected ilObjUser $user;
74  protected ?ilDBInterface $db = null;
75  protected int $ass_id = 0;
76  protected int $requested_member_id = 0;
77  protected int $requested_part_id = 0;
78  protected int $requested_ass_id = 0;
79  protected string $requested_idl_id;
80  protected bool $done = false;
82  protected string $requested_comment;
83  protected string $requested_user_login;
84  protected array $selected_participants;
85  protected array $listed_participants;
86  protected array $selected_ass_ids;
87  protected array $listed_ass_ids;
88  protected array $requested_marks; // key might be ass_ids or user_ids!
89  protected array $requested_status; // key might be ass_ids or user_ids!
90  protected array $requested_tutor_notices; // key might be ass_ids or user_ids!
91  protected array $requested_group_members; // "grpt"
93  protected array $requested_files; // "file"
94  protected string $requested_filter_status;
95  protected string $requested_filter_feedback;
96  protected GUIRequest $request;
97 
104  public function __construct(InternalService $service, ilExAssignment $a_ass = null)
105  {
106  $this->service = $service;
107  $this->gui = $gui = $service->gui();
108  $this->domain = $domain = $service->domain();
109 
110  $this->user = $domain->user();
111  $this->log = $domain->logger()->exc();
112  $this->access = $domain->access();
113  $this->lng = $domain->lng();
114 
115  $this->toolbar = $gui->toolbar();
116  $this->ui_factory = $gui->ui()->factory();
117  $this->ui_renderer = $gui->ui()->renderer();
118  $this->ctrl = $gui->ctrl();
119  $this->tabs_gui = $gui->tabs();
120  $this->tpl = $gui->ui()->mainTemplate();
121  $this->http = $gui->http();
122 
123  $this->task_factory = $domain->backgroundTasks()->taskFactory();
124  $this->request = $gui->request();
125  $request = $this->request;
126 
127  $this->exercise = $request->getExercise();
128  if ($a_ass !== null) {
129  $this->assignment = $a_ass;
130  $this->ass_id = $this->assignment->getId();
131  }
132  $this->requested_member_id = $request->getMemberId();
133  $this->requested_part_id = $request->getParticipantId();
134  $this->requested_ass_id = $request->getAssId();
135  $this->requested_idl_id = $request->getIdlId();
136  $this->done = $request->getDone();
137  $this->requested_learning_comments = $request->getLearningComments();
138  $this->requested_comment = $request->getComment();
139  $this->requested_user_login = $request->getUserLogin();
140  $this->selected_participants = $request->getSelectedParticipants();
141  $this->listed_participants = $request->getListedParticipants();
142  $this->selected_ass_ids = $request->getSelectedAssignments();
143  $this->listed_ass_ids = $request->getListedAssignments();
144  $this->requested_marks = $request->getMarks();
145  $this->requested_status = $request->getStatus();
146  $this->requested_tutor_notices = $request->getTutorNotices();
147  $this->requested_group_members = $request->getGroupMembers();
148  $this->requested_files = $request->getFiles();
149  $this->requested_filter_status = $request->getFilterStatus();
150  $this->requested_filter_feedback = $request->getFilterFeedback();
151 
152  $this->notification = $domain->notification($request->getRefId());
153 
154  $this->ctrl->saveParameter($this, array("vw", "member_id"));
155  if ($this->ass_id > 0) {
156  $this->tutor_feedback_file = $domain->assignment()->tutorFeedbackFile($this->ass_id);
157  }
158  $this->zip = $domain->resources()->zip();
159  $this->ctrl->saveParameter($this, array("part_id"));
160  }
161 
166  public function executeCommand(): void
167  {
168  $ilCtrl = $this->ctrl;
169  $lng = $this->lng;
170  $ilTabs = $this->tabs_gui;
171 
172  $class = $ilCtrl->getNextClass($this);
173  //$cmd = $ilCtrl->getCmd("listPublicSubmissions");
174 
175  switch ($class) {
176  // feedback files IRSS
177  case strtolower(ilResourceCollectionGUI::class):
178  $ilTabs->clearTargets();
179  $ilTabs->setBackTarget(
180  $lng->txt("back"),
181  $ilCtrl->getLinkTarget($this, $this->getViewBack())
182  );
183  $this->domain->assignment()->tutorFeedbackFile($this->ass_id)->addObserver();
184  $this->tpl->setOnScreenMessage('info', $lng->txt("exc_fb_tutor_info"));
185  $gui = $this->gui->assignment()->getTutorFeedbackFileResourceCollectionGUI(
186  $this->exercise->getRefId(),
187  $this->assignment->getId(),
189  );
190  $this->ctrl->forwardCommand($gui);
191  break;
192 
193  // feedback files LEGACY
194  case "ilfilesystemgui":
195  $ilTabs->clearTargets();
196  $ilTabs->setBackTarget(
197  $lng->txt("back"),
198  $ilCtrl->getLinkTarget($this, $this->getViewBack())
199  );
200 
201  $this->tpl->setOnScreenMessage('info', $lng->txt("exc_fb_tutor_info"));
202 
203  $fstorage = new ilFSStorageExercise($this->exercise->getId(), $this->assignment->getId());
204  $fstorage->create();
205 
206  $submission = new ilExSubmission($this->assignment, $this->requested_member_id);
207  $feedback_id = $submission->getFeedbackId();
208  $noti_rec_ids = $submission->getUserIds();
209 
210  $fs_title = array();
211  foreach ($noti_rec_ids as $rec_id) {
212  $fs_title[] = ilUserUtil::getNamePresentation($rec_id, false, false, "", true);
213  }
214  $fs_title = implode(" / ", $fs_title);
215 
216  $fs_gui = new ilFileSystemGUI($fstorage->getFeedbackPath($feedback_id));
217  $fs_gui->setTableId("excfbfil" . $this->assignment->getId() . "_" . $feedback_id);
218  $fs_gui->setAllowDirectories(false);
219  $fs_gui->setTitle($lng->txt("exc_fb_files") . " - " .
220  $this->assignment->getTitle() . " - " .
221  $fs_title);
222  $pcommand = $fs_gui->getLastPerformedCommand();
223  if (is_array($pcommand) && ($pcommand["cmd"] ?? "") == "create_file") {
224  foreach ($noti_rec_ids as $user_id) {
225  $member_status = $this->assignment->getMemberStatus($user_id);
226  $member_status->setFeedback(true);
227  $member_status->update();
228  }
229 
230  $this->notification->sendFeedbackNotification(
231  $this->assignment->getId(),
232  $noti_rec_ids,
233  $pcommand["name"] ?? ""
234  );
235  }
236  $this->ctrl->forwardCommand($fs_gui);
237  break;
238 
239  case 'ilrepositorysearchgui':
240  $rep_search = new ilRepositorySearchGUI();
241  $ref_id = $this->exercise->getRefId();
242  $rep_search->addUserAccessFilterCallable(function ($a_user_ids) use ($ref_id) {
243  return $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
244  'edit_submissions_grades',
245  'edit_submissions_grades',
246  $ref_id,
247  $a_user_ids
248  );
249  });
250  $rep_search->setTitle($this->lng->txt("exc_add_participant"));
251  $rep_search->setCallback($this, 'addMembersObject');
252 
253  // Set tabs
254  $this->addSubTabs("assignment");
255  $this->ctrl->setReturn($this, 'members');
256 
257  $this->ctrl->forwardCommand($rep_search);
258  break;
259 
260  case strtolower(ilRepoStandardUploadHandlerGUI::class):
261  $form = $this->getMultiFeedbackForm($this->assignment->getId());
262  $gui = $form->getRepoStandardUploadHandlerGUI("mfzip");
263  $this->ctrl->forwardCommand($gui);
264  break;
265 
266  case "ilexsubmissionteamgui":
267  $gui = new ilExSubmissionTeamGUI($this->exercise, $this->initSubmission());
268  $ilCtrl->forwardCommand($gui);
269  break;
270 
271  case "ilexsubmissionfilegui":
272  $gui = new ilExSubmissionFileGUI($this->exercise, $this->initSubmission());
273  $ilCtrl->forwardCommand($gui);
274  break;
275 
276  case "ilexsubmissiontextgui":
277  $ilCtrl->saveParameter($this, array("part_id"));
278  $gui = new ilExSubmissionTextGUI($this->exercise, $this->initSubmission());
279  $ilCtrl->forwardCommand($gui);
280  break;
281 
282  case "ilexpeerreviewgui":
283  $gui = new ilExPeerReviewGUI($this->assignment, $this->initSubmission());
284  $ilCtrl->forwardCommand($gui);
285  break;
286 
287  case "ilparticipantsperassignmenttablegui":
289  $this,
290  "members",
291  $this->exercise,
292  $this->assignment->getId()
293  );
294  $this->ctrl->forwardCommand($table);
295  break;
296 
297  default:
298  $cmd = $ilCtrl->getCmd();
299  switch ($cmd) {
300  case 'downloadSubmissions':
301  $cmd = $ilCtrl->getCmd("downloadSubmissions");
302  break;
303  default:
304  $cmd = $ilCtrl->getCmd("listPublicSubmissions");
305  break;
306  }
307  $this->{$cmd . "Object"}();
308  break;
309  }
310  }
311 
312  protected function getViewBack(): string
313  {
314  switch ($this->request->getBackView()) {
315  case self::VIEW_PARTICIPANT:
316  $back_cmd = "showParticipant";
317  break;
318 
319  case self::VIEW_GRADES:
320  $back_cmd = "showGradesOverview";
321  break;
322 
323  default:
324  // case self::VIEW_ASSIGNMENT:
325  $back_cmd = "members";
326  break;
327  }
328  return $back_cmd;
329  }
330 
331  protected function initSubmission(): ilExSubmission
332  {
333  $back_cmd = $this->getViewBack();
334  $this->ctrl->setReturn($this, $back_cmd);
335 
336  $this->tabs_gui->clearTargets();
337  $this->tabs_gui->setBackTarget(
338  $this->lng->txt("back"),
339  $this->ctrl->getLinkTarget($this, $back_cmd)
340  );
341 
342  return new ilExSubmission($this->assignment, $this->requested_member_id, null, true);
343  }
344 
345  public function addSubTabs(string $a_activate): void
346  {
347  $ilTabs = $this->tabs_gui;
348  $lng = $this->lng;
349  $ilCtrl = $this->ctrl;
350 
351  $ass_id = $this->assignment !== null ? $this->assignment->getId() : 0;
352  $part_id = $this->requested_part_id;
353 
354  $ilCtrl->setParameter($this, "vw", "");
355  $ilCtrl->setParameter($this, "member_id", "");
356  $ilCtrl->setParameter($this, "ass_id", "");
357  $ilCtrl->setParameter($this, "part_id", "");
358 
359  $ilTabs->addSubTab(
360  "assignment",
361  $lng->txt("exc_assignment_view"),
362  $ilCtrl->getLinkTarget($this, "members")
363  );
364  $ilTabs->addSubTab(
365  "participant",
366  $lng->txt("exc_participant_view"),
367  $ilCtrl->getLinkTarget($this, "showParticipant")
368  );
369  $ilTabs->addSubTab(
370  "grades",
371  $lng->txt("exc_grades_overview"),
372  $ilCtrl->getLinkTarget($this, "showGradesOverview")
373  );
374  $ilTabs->activateSubTab($a_activate);
375 
376  $ilCtrl->setParameter($this, "ass_id", $ass_id);
377  $ilCtrl->setParameter($this, "part_id", $part_id);
378  }
379 
383  public function waitingDownloadObject(): void
384  {
385  $lng = $this->lng;
386  $ilCtrl = $this->ctrl;
387 
388  $ilCtrl->setParameterByClass("ilExSubmissionFileGUI", "member_id", $this->requested_member_id);
389  $url = $ilCtrl->getLinkTargetByClass(array("ilExerciseHandlerGUI", "ilObjExerciseGUI", "ilExerciseManagementGUI", "ilExSubmissionFileGUI"), "downloadNewReturned");
390  $js_url = $ilCtrl->getLinkTargetByClass(array("ilExerciseHandlerGUI", "ilObjExerciseGUI", "ilExerciseManagementGUI", "ilExSubmissionFileGUI"), "downloadNewReturned", "", "", false);
391  $this->tpl->setOnScreenMessage('info', $lng->txt("exc_wait_for_files") . "<a href='$url'> " . $lng->txt('exc_download_files') . "</a><script>window.location.href ='" . $js_url . "';</script>");
392  $this->membersObject();
393  }
394 
399  public function membersObject(): void
400  {
401  $tpl = $this->tpl;
402  $ilToolbar = $this->toolbar;
403  $ilCtrl = $this->ctrl;
404  $lng = $this->lng;
405 
406  $this->addSubTabs("assignment");
407  $this->gui->permanentLink()->setGradesPermanentLink();
408 
409  // assignment selection
410  $ass = ilExAssignment::getInstancesByExercise($this->exercise->getId());
411 
412  if ($this->assignment === null && count($ass) > 0) {
413  $this->assignment = current($ass);
414  }
415 
416  reset($ass);
417  if (count($ass) > 1) {
418  $options = array();
419  foreach ($ass as $a) {
420  $options[$a->getId()] = $a->getTitle();
421  }
422  $si = new ilSelectInputGUI($this->lng->txt("exc_assignment"), "ass_id");
423  $si->setOptions($options);
424  $si->setValue($this->assignment->getId());
425  $ilToolbar->addStickyItem($si, true);
426  $this->gui->button(
427  $this->lng->txt("select"),
428  "selectAssignment"
429  )->submit()->toToolbar(true);
430 
431  $ilToolbar->addSeparator();
432  }
433  // #16165 - if only 1 assignment dropdown is not displayed;
434  elseif ($this->assignment) {
435  $ilCtrl->setParameter($this, "ass_id", $this->assignment->getId());
436  }
437 
438  // add member
439  // is only shown if 'edit_submissions_grades' is granted by rbac. positions
440  // access is not sufficient.
441  $has_rbac_access = $GLOBALS['DIC']->access()->checkAccess(
442  'edit_submissions_grades',
443  '',
444  $this->exercise->getRefId()
445  );
446  if ($has_rbac_access) {
448  $this,
449  $ilToolbar,
450  array(
451  'auto_complete_name' => $lng->txt('user'),
452  'submit_name' => $lng->txt('add'),
453  'add_search' => true,
454  'add_from_container' => $this->exercise->getRefId()
455  )
456  );
457  }
458 
459  // #16168 - no assignments
460  if ($ass !== []) {
461  if ($has_rbac_access) {
462  $ilToolbar->addSeparator();
463  }
464 
465  // we do not want the ilRepositorySearchGUI form action
466  $ilToolbar->setFormAction($ilCtrl->getFormAction($this));
467 
468  $ilCtrl->setParameter($this, "ass_id", $this->assignment->getId());
469 
470  if ($this->assignment->getType() == ilExAssignment::TYPE_UPLOAD_TEAM) {
471  if (ilExAssignmentTeam::getAdoptableGroups($this->exercise->getRefId())) {
472  $ilToolbar->addButton(
473  $this->lng->txt("exc_adopt_group_teams"),
474  $this->ctrl->getLinkTarget($this, "adoptTeamsFromGroup")
475  );
476 
477  $ilToolbar->addSeparator();
478  }
479  } elseif ($this->exercise->hasTutorFeedbackFile()) {
480  if (!$this->assignment->getAssignmentType()->usesTeams()) {
481  // multi-feedback
482  $ilToolbar->addButton(
483  $this->lng->txt("exc_multi_feedback"),
484  $this->ctrl->getLinkTarget($this, "showMultiFeedback")
485  );
486 
487  $ilToolbar->addSeparator();
488  }
489  }
490 
491  $submission_repository = $this->service->repo()->submission();
492 
493  if ($submission_repository->hasSubmissions($this->assignment->getId()) !== 0) {
494  $ass_type = $this->assignment->getType();
495  //todo change addFormButton for addButtonInstance
496  if ($ass_type == ilExAssignment::TYPE_TEXT) {
497  $ilToolbar->addFormButton($lng->txt("exc_list_text_assignment"), "listTextAssignment");
498  }
499  $ilToolbar->addFormButton($lng->txt("download_all_returned_files"), "downloadSubmissions");
500  }
501  $this->ctrl->setParameter($this, "vw", self::VIEW_ASSIGNMENT);
502 
503  $exc_tab = new ilParticipantsPerAssignmentTableGUI($this, "members", $this->exercise, $this->assignment->getId());
504  $tpl->setContent(
505  $exc_tab->getHTML() .
507  );
508  } else {
509  $this->tpl->setOnScreenMessage('info', $lng->txt("exc_no_assignments_available"));
510  }
511 
512  $ilCtrl->setParameter($this, "ass_id", "");
513  }
514 
515  public function downloadSelectedObject(): void
516  {
517  if (count($this->selected_participants) > 0) {
518  $this->downloadSubmissionsObject($this->selected_participants);
519  } else {
520  $this->backToCurrentOverview();
521  }
522  }
523  public function downloadSubmissionsObject(?array $selected_participants = null): void
524  {
525  $participant_id = $this->requested_part_id;
526 
527  $download_task = new ilDownloadSubmissionsBackgroundTask(
528  (int) $GLOBALS['DIC']->user()->getId(),
529  $this->exercise->getRefId(),
530  $this->exercise->getId(),
532  $participant_id,
534  );
535 
536  if ($download_task->run()) {
537  $this->tpl->setOnScreenMessage('success', $this->lng->txt('exc_down_files_started_bg'), true);
538  }
539 
540  $this->backToCurrentOverview();
541  }
542 
543  public function backToCurrentOverview(): void
544  {
545  if ($this->assignment !== null) {
546  $this->ctrl->redirect($this, "members");
547  } else {
548  $this->ctrl->redirect($this, "showParticipant");
549  }
550  }
551 
555  public function membersApplyObject(): void
556  {
557  $this->saveStatusAllObject(null, false);
558  $exc_tab = new ilParticipantsPerAssignmentTableGUI($this, "members", $this->exercise, $this->assignment->getId());
559  $exc_tab->resetOffset();
560  $exc_tab->writeFilterToSession();
561 
562  $this->membersObject();
563  }
564 
568  public function membersResetObject(): void
569  {
570  $exc_tab = new ilParticipantsPerAssignmentTableGUI($this, "members", $this->exercise, $this->assignment->getId());
571  $exc_tab->resetOffset();
572  $exc_tab->resetFilter();
573 
574  $this->membersObject();
575  }
576 
577  public function saveGradesObject(): void
578  {
579  $ilCtrl = $this->ctrl;
580  $lng = $this->lng;
581 
582  foreach ($this->requested_learning_comments as $k => $v) {
583  $marks_obj = new ilLPMarks($this->exercise->getId(), (int) $k);
584  $marks_obj->setComment($v);
585  $marks_obj->update();
586  }
587  foreach ($this->requested_marks as $k => $v) {
588  $marks_obj = new ilLPMarks($this->exercise->getId(), (int) $k);
589  $marks_obj->setMark($v);
590  $marks_obj->update();
591  }
592  $this->tpl->setOnScreenMessage('success', $lng->txt("exc_msg_saved_grades"), true);
593  $ilCtrl->redirect($this, "showGradesOverview");
594  }
595 
596 
597  // TEXT ASSIGNMENT ?!
598 
603  public function listTextAssignmentObject(): void
604  {
605  $this->initFilter();
606  $this->setBackToMembers();
607 
609  $button_print = $this->ui_factory->button()->standard($this->lng->txt('print'), "#")
610  ->withOnLoadCode(function ($id) {
611  return "$('#$id').click(function() { window.print(); return false; });";
612  });
613  $this->toolbar->addSeparator();
614  $this->toolbar->addComponent($button_print);
615 
616  $group_panels_tpl = new ilTemplate("tpl.exc_group_report_panels.html", true, true, "Modules/Exercise");
617  $group_panels_tpl->setVariable('TITLE', $this->lng->txt("exc_list_text_assignment") . ": " . $this->assignment->getTitle());
618 
619  $report_html = "";
620  $total_reports = 0;
621 
622  $members = ilExSubmission::getAssignmentParticipants($this->exercise->getId(), $this->ass_id);
623  $members_filter = new ilExerciseMembersFilter($this->exercise->getRefId(), $members, $this->user->getId());
624  $members = $members_filter->filterParticipantsByAccess();
625 
626  foreach (ilExSubmission::getAssignmentFilesByUsers($this->exercise->getId(), $this->assignment->getId(), $members) as $file) {
627  if (trim($file["atext"]) && ilObjUser::_exists($file["user_id"])) {
628  $feedback_data = $this->collectFeedbackDataFromPeer($file);
629  $submission_data = $this->assignment->getExerciseMemberAssignmentData((int) $file["user_id"], $this->filter["status"] ?? "");
630 
631  if (is_array($submission_data)) {
632  $data = array_merge($feedback_data, $submission_data);
633  $report_html .= $this->getReportPanel($data);
634  $total_reports++;
635  }
636  }
637  }
638  if ($total_reports == 0) {
639  $mess = $this->ui_factory->messageBox()->info($this->lng->txt("fiter_no_results"));
640  $report_html .= $this->ui_renderer->render($mess);
641  }
642 
643  $group_panels_tpl->setVariable('CONTENT', $report_html);
644  $this->tpl->setContent($group_panels_tpl->get());
645  }
646 
652  public function compareTextAssignmentsObject(): void
653  {
654  $this->setBackToMembers();
655 
656  $group_panels_tpl = new ilTemplate("tpl.exc_group_report_panels.html", true, true, "Modules/Exercise");
657  $group_panels_tpl->setVariable('TITLE', $this->lng->txt("exc_compare_selected_submissions"));
658 
659  $report_html = "";
660  //participant ids selected via checkboxes
661  $participants = array_keys($this->getMultiActionUserIds());
662 
663  $total_reports = 0;
664  foreach ($participants as $participant_id) {
665  $submission = new ilExSubmission($this->assignment, $participant_id);
666 
667  //submission data array
668  $files = $submission->getFiles();
669  $file = reset($files);
670 
671  if (!$file) {
672  $file = [
673  "user_id" => $participant_id,
674  "ts" => null,
675  "atext" => null
676  ];
677  }
678 
679  $feedback_data = $this->collectFeedbackDataFromPeer($file);
680 
681  $submission_data = $this->assignment->getExerciseMemberAssignmentData((int) $file["user_id"], $this->filter["status"] ?? "");
682 
683  if (is_array($submission_data)) {
684  $data = array_merge($feedback_data, $submission_data);
685  $report_html .= $this->getReportPanel($data);
686  $total_reports++;
687  }
688  }
689 
690  $group_panels_tpl->setVariable('CONTENT', $report_html);
691  $this->tpl->setContent($group_panels_tpl->get());
692  }
693 
697  public function getReportPanel(array $a_data): string
698  {
699  $modal = $this->getEvaluationModal($a_data);
700 
701  $this->ctrl->setParameter($this, "member_id", $a_data['uid']);
702  $actions = array(
703  $this->ui_factory->button()->shy($this->lng->txt("grade_evaluate"), "#")->withOnClick($modal->getShowSignal())
704  );
705 
706  if ($this->exercise->hasTutorFeedbackMail()) {
707  $actions[] = $this->ui_factory->button()->shy(
708  $this->lng->txt("exc_tbl_action_feedback_mail"),
709  $this->ctrl->getLinkTarget($this, "redirectFeedbackMail")
710  );
711  }
712  if ($this->exercise->hasTutorFeedbackFile()) {
713  $actions[] = $this->ui_factory->button()->shy(
714  $this->lng->txt("exc_tbl_action_feedback_file"),
715  $this->ctrl->getLinkTargetByClass("ilFileSystemGUI", "listFiles")
716  );
717  }
718 
719  $this->ctrl->setParameter($this, "member_id", "");
720 
721  $actions_dropdown = $this->ui_factory->dropdown()->standard($actions);
722  if ($a_data['status'] == self::GRADE_NOT_GRADED) {
723  $str_status_key = $this->lng->txt('exc_tbl_status') . ": ";
724  $str_status_value = "-";
725  } else {
726  $str_status_key = $this->lng->txt('exc_tbl_status_time') . ": ";
727  $str_status_value = ilDatePresentation::formatDate(new ilDateTime($a_data["status_time"], IL_CAL_DATETIME));
728  }
729 
730  $str_mark_key = $this->lng->txt("exc_tbl_mark") . ": ";
731  $str_mark_value = "-";
732 
733  if (($a_data['mark'] != "")) {
734  $str_mark_value = $a_data['mark'];
735  }
736 
737  if ($a_data['feedback_time']) {
738  $str_evaluation_key = $this->lng->txt('exc_tbl_feedback_time') . ": ";
739  $str_evaluation_value = ilDatePresentation::formatDate(new ilDateTime($a_data["feedback_time"], IL_CAL_DATETIME));
740  } else {
741  $str_evaluation_key = $this->lng->txt('exc_settings_feedback') . ": ";
742  $str_evaluation_value = "-";
743  }
744 
745  $card_content = array(
746  $this->lng->txt("exc_tbl_submission_date") . ": " => ilDatePresentation::formatDate(new ilDateTime($a_data["udate"], IL_CAL_DATETIME)),
747  $str_status_key => $str_status_value,
748  $str_mark_key => $str_mark_value,
749  $str_evaluation_key => $str_evaluation_value,
750  $this->lng->txt('feedback_given') . ": " => $a_data['fb_given'],
751  $this->lng->txt('feedback_received') . ": " => $a_data['fb_received']
752  );
753  $card_tpl = new ilTemplate("tpl.exc_report_details_card.html", true, true, "Modules/Exercise");
754  foreach ($card_content as $key => $value) {
755  $card_tpl->setCurrentBlock("assingment_card");
756  $card_tpl->setVariable("ROW_KEY", $key);
757  $card_tpl->setVariable("ROW_VALUE", $value);
758  $card_tpl->parseCurrentBlock();
759  }
760 
761  $main_panel = $this->ui_factory->panel()->sub($a_data['uname'], $this->ui_factory->legacy($a_data['utext']))
762  ->withFurtherInformation($this->ui_factory->card()->standard($this->lng->txt('text_assignment'))->withSections(array($this->ui_factory->legacy($card_tpl->get()))))->withActions($actions_dropdown);
763 
764  $feedback_tpl = new ilTemplate("tpl.exc_report_feedback.html", true, true, "Modules/Exercise");
765  //if no feedback filter the feedback is displayed. Can be list submissions or compare submissions.
766  $filter_feedback = $this->filter["feedback"] ?? "";
767  if (array_key_exists("peer", $a_data) && (($filter_feedback == self::FEEDBACK_FULL_SUBMISSION) || $filter_feedback == "")) {
768  $feedback_tpl->setCurrentBlock("feedback");
769  foreach ($a_data["peer"] as $peer_id) {
770  if (ilObject::_lookupType($peer_id) == "usr") {
771  $user = new ilObjUser($peer_id);
772  $peer_name = $user->getFirstname() . " " . $user->getLastname();
773  } else {
774  $peer_name = $this->lng->txt("exc_deleted_user");
775  }
776 
777  $feedback_tpl->setCurrentBlock("peer_feedback");
778  $feedback_tpl->setVariable("PEER_NAME", $peer_name);
779 
780  $submission = new ilExSubmission($this->assignment, $a_data["uid"]);
781  $values = $submission->getPeerReview()->getPeerReviewValues($peer_id, $a_data["uid"]);
782 
783  $review_html = "";
784  foreach ($this->assignment->getPeerReviewCriteriaCatalogueItems() as $crit) {
785  $crit_id = $crit->getId()
786  ? $crit->getId()
787  : $crit->getType();
788  $crit->setPeerReviewContext($this->assignment, $peer_id, $a_data["uid"]);
789 
790  $review_html .=
791  '<div class="ilBlockPropertyCaption">' . $crit->getTitle() . '</div>' .
792  '<div style="margin:2px 0;">' . $crit->getHTML($values[$crit_id] ?? null) . '</div>';
793  }
794  $feedback_tpl->setVariable("PEER_FEEDBACK", $review_html);
795  $feedback_tpl->parseCurrentBlock();
796  }
797  $feedback_tpl->parseCurrentBlock();
798  }
799  $feedback_tpl->setVariable("GRADE", $this->lng->txt('exc_grading') . ": " . $this->lng->txt('exc_' . $a_data['status']));
800  $comment = ($a_data['comment'] === "")
801  ? "-"
802  : $a_data['comment'];
803  $feedback_tpl->setVariable("COMMENT", $this->lng->txt('exc_comment') . ": <br>" . $comment);
804 
805  $feedback_panel = $this->ui_factory->panel()->sub("", $this->ui_factory->legacy($feedback_tpl->get()));
806 
807  $report = $this->ui_factory->panel()->report("", array($main_panel, $feedback_panel));
808 
809  return $this->ui_renderer->render([$modal,$report]);
810  }
811 
812  public function getEvaluationModal(
813  array $a_data
814  ): RoundTrip {
815  $modal_tpl = new ilTemplate("tpl.exc_report_evaluation_modal.html", true, true, "Modules/Exercise");
816  $modal_tpl->setVariable("USER_NAME", $a_data['uname']);
817 
818  $form = $this->getEvaluationModalForm($a_data);
819  //TODO: CHECK ilias string utils. ilUtil shortenText with net blank.
820  if ($this->exercise->hasTutorFeedbackText()) {
821  $max_chars = 500;
822 
823  $u_text = strip_tags($a_data["utext"]); //otherwise will get open P
824  $text = $u_text;
825  //show more
826  if (strlen($u_text) > $max_chars) {
827  $text = "<input type='checkbox' class='read-more-state' id='post-1' />";
828  $text .= "<div class='read-more-wrap'>";
829  $text .= mb_substr($u_text, 0, $max_chars);
830  $text .= "<span class='read-more-target'>";
831  $text .= mb_substr($u_text, $max_chars);
832  $text .= "</span></div>";
833  $text .= "<label for='post-1' class='read-more-trigger'></label>";
834  }
835  $modal_tpl->setVariable("USER_TEXT", $text);
836  }
837 
838  $modal_tpl->setVariable("FORM", $form->getHTML());
839 
840  $form_id = 'form_' . $form->getId();
841  $submit_btn = $this->ui_factory->button()->primary($this->lng->txt("save"), '#')
842  ->withOnLoadCode(function ($id) use ($form_id) {
843  return "$('#$id').click(function() { $('#$form_id').submit(); return false; });";
844  });
845 
846  return $this->ui_factory->modal()->roundtrip(strtoupper($this->lng->txt("grade_evaluate")), $this->ui_factory->legacy($modal_tpl->get()))->withActionButtons([$submit_btn]);
847  }
848 
849  public function getEvaluationModalForm(
850  array $a_data
851  ): ilPropertyFormGUI {
852  $form = new ilPropertyFormGUI();
853  $form->setFormAction($this->ctrl->getFormAction($this, "saveEvaluationFromModal"));
854  $form->setId(uniqid('form'));
855 
856  //Grade
857  $options = array(
858  self::GRADE_NOT_GRADED => $this->lng->txt("exc_notgraded"),
859  self::GRADE_PASSED => $this->lng->txt("exc_passed"),
860  self::GRADE_FAILED => $this->lng->txt("exc_failed")
861  );
862  $si = new ilSelectInputGUI($this->lng->txt("exc_tbl_status"), "grade");
863  $si->setOptions($options);
864  $si->setValue($a_data['status'] ?? "");
865  $form->addItem($si);
866 
867  //Mark
868  $mark_input = new ilTextInputGUI($this->lng->txt("exc_tbl_mark"), "mark");
869  $mark_input->setValue($a_data['mark'] ?? "");
870  $mark_input->setMaxLength(32);
871  $mark_input->setSize(4);
872  $form->addItem($mark_input);
873 
874  $item = new ilHiddenInputGUI('mem_id');
875  $item->setValue($a_data['uid'] ?? "");
876  $form->addItem($item);
877 
878  //TODO: CHECK ilias string utils. ilUtil shortenText with net blank.
879  if ($this->exercise->hasTutorFeedbackText()) {
880  $ta = new ilTextAreaInputGUI($this->lng->txt("exc_comment"), 'comment');
881  $ta->setInfo($this->lng->txt("exc_comment_for_learner_info"));
882  $ta->setValue($a_data['comment'] ?? "");
883  $ta->setRows(10);
884  $form->addItem($ta);
885  }
886  return $form;
887  }
888 
889  // Save assignment submission grade(status) and comment from the roundtrip modal.
890 
894  public function saveEvaluationFromModalObject(): void
895  {
896  $form = $this->getEvaluationModalForm([]);
897  $user_id = 0;
898  $comment = "";
899  $mark = "";
900  $grade = "";
901  if ($form->checkInput()) {
902  $comment = trim($form->getInput('comment'));
903  $user_id = (int) $form->getInput('mem_id');
904  $grade = trim($form->getInput('grade'));
905  $mark = trim($form->getInput('mark'));
906  }
907 
908  if ($this->assignment->getId() && $user_id > 0) {
909  $member_status = $this->assignment->getMemberStatus($user_id);
910  $member_status->setComment(ilUtil::stripSlashes($comment));
911  if ($grade != "") {
912  $member_status->setStatus($grade);
913  }
914  $member_status->setMark($mark);
915  if ($comment != "") {
916  $member_status->setFeedback(true);
917  }
918  $member_status->update();
919  }
920  $this->tpl->setOnScreenMessage('success', $this->lng->txt("exc_status_saved"), true);
921  $this->ctrl->redirect($this, "listTextAssignment");
922  }
923 
924  // Add user as member
925 
929  public function addUserFromAutoCompleteObject(): void
930  {
931  if ($this->requested_user_login == "") {
932  $this->tpl->setOnScreenMessage('failure', $this->lng->txt('msg_no_search_string'));
933  $this->membersObject();
934  return;
935  }
936  $users = explode(',', $this->requested_user_login);
937 
938  $user_ids = array();
939  foreach ($users as $user) {
940  $user_id = ilObjUser::_lookupId($user);
941 
942  if (!$user_id) {
943  $this->tpl->setOnScreenMessage('failure', $this->lng->txt('user_not_known'));
944  $this->membersObject();
945  return;
946  }
947 
948  $user_ids[] = $user_id;
949  }
950 
951  $this->addMembersObject($user_ids);
952  }
953 
954  // Add new partipant
955  public function addMembersObject($a_user_ids = array()): void
956  {
957  if (!count($a_user_ids)) {
958  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("no_checkbox"), true);
959  } else {
960  if (!$this->exercise->members_obj->assignMembers($a_user_ids)) {
961  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("exc_members_already_assigned"), true);
962  } else {
963  $this->tpl->setOnScreenMessage('success', $this->lng->txt("exc_members_assigned"), true);
964  }
965  }
966  $this->ctrl->redirect($this, "members");
967  }
968 
972  public function selectAssignmentObject(): void
973  {
974  $ctrl = $this->ctrl;
975  $ctrl->setParameter($this, "ass_id", $this->requested_ass_id);
976  $ctrl->redirect($this, "members");
977  }
978 
982  public function showParticipantObject(): void
983  {
984  $tpl = $this->tpl;
985  $ilToolbar = $this->toolbar;
986  $ilCtrl = $this->ctrl;
987  $lng = $this->lng;
988  $access = $this->access;
989 
990  $this->addSubTabs("participant");
991  $this->ctrl->setParameter($this, "ass_id", "");
992 
993  // participant selection
994  $members = $this->exercise->members_obj->getMembers();
995 
996  $members = $access->filterUserIdsByRbacOrPositionOfCurrentUser(
997  'edit_submissions_grades',
998  'edit_submissions_grades',
999  $this->exercise->getRefId(),
1000  $members
1001  );
1002 
1003 
1004  if (count($members) == 0) {
1005  $this->tpl->setOnScreenMessage('info', $lng->txt("exc_no_participants"));
1006  return;
1007  }
1008 
1009  $mems = array();
1010  foreach ($members as $mem_id) {
1011  if (ilObject::_lookupType($mem_id) == "usr") {
1012  $name = ilObjUser::_lookupName($mem_id);
1013  if (trim($name["login"]) != "") { // #20073
1014  $mems[$mem_id] = $name;
1015  }
1016  }
1017  }
1018 
1019  $mems = ilArrayUtil::sortArray($mems, "lastname", "asc", false, true);
1020 
1021  if ($this->requested_part_id == 0 && $mems !== [] && key($mems) > 0) {
1022  $ilCtrl->setParameter($this, "part_id", key($mems));
1023  $ilCtrl->redirect($this, "showParticipant");
1024  }
1025 
1026  $current_participant = $this->requested_part_id;
1027 
1028  reset($mems);
1029  if (count($mems) > 1) {
1030  $options = array();
1031  foreach ($mems as $k => $m) {
1032  $options[$k] =
1033  $m["lastname"] . ", " . $m["firstname"] . " [" . $m["login"] . "]";
1034  }
1035  $si = new ilSelectInputGUI($this->lng->txt("exc_participant"), "part_id");
1036  $si->setOptions($options);
1037  $si->setValue($current_participant);
1038  $ilToolbar->addStickyItem($si, true);
1039 
1040  $this->gui->button(
1041  $this->lng->txt("select"),
1042  "selectParticipant"
1043  )->submit()->toToolbar(true);
1044  }
1045 
1046  if ($mems !== []) {
1047  $this->ctrl->setParameter($this, "vw", self::VIEW_PARTICIPANT);
1048  $this->ctrl->setParameter($this, "part_id", $current_participant);
1049 
1050  $ilToolbar->addSeparator();
1051  $ilToolbar->setFormAction($ilCtrl->getFormAction($this));
1052  $ilToolbar->addFormButton($lng->txt("download_all_returned_files"), "downloadSubmissions");
1053 
1054  $part_tab = new ilAssignmentsPerParticipantTableGUI(
1055  $this,
1056  "showParticipant",
1057  $this->exercise,
1058  $current_participant
1059  );
1060  $tpl->setContent($part_tab->getHTML() .
1061  $this->initIndividualDeadlineModal());
1062  } else {
1063  $this->tpl->setOnScreenMessage('info', $this->lng->txt("exc_no_assignments_available"));
1064  }
1065  }
1066 
1069  public function showParticipantApplyObject(): void
1070  {
1071  $exc_tab = new ilAssignmentsPerParticipantTableGUI($this, "showParticipant", $this->exercise, $this->requested_part_id);
1072  $exc_tab->resetOffset();
1073  $exc_tab->writeFilterToSession();
1074 
1075  $this->showParticipantObject();
1076  }
1077 
1080  public function showParticipantResetObject(): void
1081  {
1082  $exc_tab = new ilAssignmentsPerParticipantTableGUI($this, "showParticipant", $this->exercise, $this->requested_part_id);
1083  $exc_tab->resetOffset();
1084  $exc_tab->resetFilter();
1085 
1086  $this->showParticipantObject();
1087  }
1088 
1092  public function selectParticipantObject(): void
1093  {
1094  $ctrl = $this->ctrl;
1095  $ctrl->setParameter($this, "part_id", $this->requested_part_id);
1096  $ctrl->redirect($this, "showParticipant");
1097  }
1098 
1099  public function showGradesOverviewObject(): void
1100  {
1101  $tpl = $this->tpl;
1102  $ilToolbar = $this->toolbar;
1103  $ilCtrl = $this->ctrl;
1104  $lng = $this->lng;
1105 
1106  $this->addSubTabs("grades");
1107 
1108  $mem_obj = new ilExerciseMembers($this->exercise);
1109  $mems = $mem_obj->getMembers();
1110 
1111  $mems = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
1112  'edit_submissions_grades',
1113  'edit_submissions_grades',
1114  $this->exercise->getRefId(),
1115  $mems
1116  );
1117  if (count($mems) > 0) {
1118  $ilToolbar->addButton(
1119  $lng->txt("exc_export_excel"),
1120  $ilCtrl->getLinkTarget($this, "exportExcel")
1121  );
1122  }
1123 
1124  $this->ctrl->setParameter($this, "vw", self::VIEW_GRADES);
1125 
1126  $grades_tab = new ilExGradesTableGUI(
1127  $this,
1128  "showGradesOverview",
1129  $this->service,
1130  $mem_obj
1131  );
1132  $tpl->setContent($grades_tab->getHTML());
1133  }
1134 
1138  public function redirectFeedbackMailObject(): void
1139  {
1140  if ($this->requested_member_id > 0) {
1141  $submission = new ilExSubmission($this->assignment, $this->requested_member_id);
1142  $members = $submission->getUserIds();
1143  } elseif ($members = $this->getMultiActionUserIds()) {
1144  $members = array_keys($members);
1145  }
1146 
1147  if ($members !== []) {
1148  $logins = array();
1149  foreach ($members as $user_id) {
1150  $member_status = $this->assignment->getMemberStatus($user_id);
1151  $member_status->setFeedback(true);
1152  $member_status->update();
1153 
1154  $logins[] = ilObjUser::_lookupLogin($user_id);
1155  }
1156  $logins = implode(",", $logins);
1157 
1158  // #16530 - see ilObjCourseGUI::createMailSignature
1159  $sig = chr(13) . chr(10) . chr(13) . chr(10);
1160  $sig .= $this->lng->txt('exc_mail_permanent_link');
1161  $sig .= chr(13) . chr(10) . chr(13) . chr(10);
1162  $sig .= ilLink::_getLink($this->exercise->getRefId());
1163  $sig = rawurlencode(base64_encode($sig));
1164 
1166  $this,
1167  $this->getViewBack(),
1168  array(),
1169  array(
1170  'type' => 'new',
1171  'rcp_to' => $logins,
1173  )
1174  ));
1175  }
1176  }
1177 
1178  // Download all submitted files (of all members).
1179 
1185  public function downloadAllObject(): void
1186  {
1187  $members = array();
1188 
1189  foreach ($this->exercise->members_obj->getMembers() as $member_id) {
1190  $submission = new ilExSubmission($this->assignment, $member_id);
1191  $submission->updateTutorDownloadTime();
1192 
1193  // get member object (ilObjUser)
1194  if (ilObject::_exists($member_id)) {
1195  $storage_id = "";
1196  // adding file metadata
1197  foreach ($submission->getFiles() as $file) {
1198  if ($this->assignment->getAssignmentType()->isSubmissionAssignedToTeam()) {
1199  $storage_id = $file["team_id"];
1200  } else {
1201  $storage_id = $file["user_id"];
1202  }
1203 
1204  $members[$storage_id]["files"][$file["returned_id"]] = $file;
1205  }
1206  if ($this->assignment->getAssignmentType()->isSubmissionAssignedToTeam()) {
1207  $name = "Team " . $submission->getTeam()->getId();
1208  } else {
1210  $tmp_obj = ilObjectFactory::getInstanceByObjId($member_id);
1211  $name = $tmp_obj->getFirstname() . " " . $tmp_obj->getLastname();
1212  }
1213  if ($storage_id > 0) {
1214  $members[$storage_id]["name"] = $name;
1215  }
1216  unset($tmp_obj);
1217  }
1218  }
1219 
1220  ilExSubmission::downloadAllAssignmentFiles($this->assignment, $members, "");
1221  }
1222 
1226  protected function getMultiActionUserIds(bool $a_keep_teams = false): array
1227  {
1228  $members = [];
1229  // multi-user
1230  if ($this->assignment !== null) {
1231  if (count($this->selected_participants) == 0) {
1232  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("no_checkbox"), true);
1233  $this->ctrl->redirect($this, "members");
1234  }
1235 
1236  foreach ($this->selected_participants as $user_id) {
1237  $submission = new ilExSubmission($this->assignment, $user_id);
1238  $tmembers = $submission->getUserIds();
1239  if (!$a_keep_teams) {
1240  foreach ($tmembers as $tuser_id) {
1241  $members[$tuser_id] = 1;
1242  }
1243  } else {
1244  if ($tmembers) {
1245  $members[] = $tmembers;
1246  } else {
1247  // no team yet
1248  $members[] = $user_id;
1249  }
1250  }
1251  }
1252  }
1253  // multi-ass
1254  else {
1255  if (count($this->selected_ass_ids) == 0) {
1256  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("no_checkbox"), true);
1257  $this->ctrl->redirect($this, "showParticipant");
1258  }
1259 
1260  $user_id = $this->requested_part_id;
1261 
1262  foreach ($this->selected_ass_ids as $ass_id) {
1263  $submission = new ilExSubmission(new ilExAssignment($ass_id), $user_id);
1264  $tmembers = $submission->getUserIds();
1265  if (!$a_keep_teams) {
1266  foreach ($tmembers as $tuser_id) {
1267  $members[$ass_id][] = $tuser_id;
1268  }
1269  } else {
1270  if ($tmembers) {
1271  $members[$ass_id][] = $tmembers;
1272  } else {
1273  // no team yet
1274  $members[$ass_id][] = $user_id;
1275  }
1276  }
1277  }
1278  }
1279 
1280  return $members;
1281  }
1282 
1283  // Send assignment per mail to participants
1284 
1288  public function sendMembersObject(): void
1289  {
1290  $members = $this->getMultiActionUserIds();
1291 
1292  $this->tpl->setOnScreenMessage('success', $this->lng->txt("exc_sent"), true);
1293  if ($this->assignment !== null) {
1294  $this->exercise->sendAssignment($this->assignment, array_keys($members));
1295  $this->ctrl->redirect($this, "members");
1296  } else {
1297  foreach ($members as $ass_id => $users) {
1298  $this->exercise->sendAssignment(new ilExAssignment($ass_id), $users);
1299  }
1300  $this->ctrl->setParameter($this, "part_id", $this->requested_part_id); // #17629
1301  $this->ctrl->redirect($this, "showParticipant");
1302  }
1303  }
1304 
1308  public function confirmDeassignMembersObject(): void
1309  {
1310  $ilCtrl = $this->ctrl;
1311  $tpl = $this->tpl;
1312  $lng = $this->lng;
1313 
1314  $members = $this->getMultiActionUserIds();
1315 
1316  $cgui = new ilConfirmationGUI();
1317  $cgui->setFormAction($ilCtrl->getFormAction($this));
1318  $cgui->setHeaderText($lng->txt("exc_msg_sure_to_deassign_participant"));
1319  $cgui->setCancel($lng->txt("cancel"), "members");
1320  $cgui->setConfirm($lng->txt("remove"), "deassignMembers");
1321  foreach ($members as $k => $m) {
1322  $cgui->addItem(
1323  "member_ids[]",
1324  $k,
1325  ilUserUtil::getNamePresentation((int) $k, false, false, "", true)
1326  );
1327  }
1328 
1329  $tpl->setContent($cgui->getHTML());
1330  }
1331 
1332  // Deassign members from exercise
1333 
1337  public function deassignMembersObject(): void
1338  {
1339  $ilCtrl = $this->ctrl;
1340  $lng = $this->lng;
1341 
1342  $member_ids = $this->request->getMemberIds();
1343 
1344  foreach ($member_ids as $usr_id) {
1345  $this->exercise->members_obj->deassignMember((int) $usr_id);
1346  $this->removeUserSubmissionFilesFromWebDir((int) $usr_id);
1347  }
1348  $this->tpl->setOnScreenMessage('success', $lng->txt("exc_msg_participants_removed"), true);
1349  $ilCtrl->redirect($this, "members");
1350  }
1351 
1352  public function removeUserSubmissionFilesFromWebDir(int $user_id): void
1353  {
1354  $storage = new ilFSWebStorageExercise($this->exercise->getId(), $this->ass_id);
1355  $storage->deleteUserSubmissionDirectory($user_id);
1356  }
1357 
1362  public function saveStatusParticipantObject(array $selected_ass_ids = null): void
1363  {
1364  $ilCtrl = $this->ctrl;
1365 
1366  $member_id = $this->requested_part_id;
1367  $data = array();
1368  $marks = $this->requested_marks;
1369  $status = $this->requested_status;
1370  $notices = $this->requested_tutor_notices;
1371  foreach ($this->listed_ass_ids as $ass_id) {
1372  if (is_array($selected_ass_ids) &&
1373  !in_array($ass_id, $selected_ass_ids)) {
1374  continue;
1375  }
1376 
1377  $data[$ass_id][$member_id] = array(
1378  "status" => $status[$ass_id]
1379  );
1380  if (isset($marks[$ass_id])) {
1381  $data[$ass_id][$member_id]["mark"] = $marks[$ass_id];
1382  }
1383  if (isset($notices[$ass_id])) {
1384  $data[$ass_id][$member_id]["notice"] = $notices[$ass_id];
1385  }
1386  }
1387 
1388  $ilCtrl->setParameter($this, "part_id", $member_id); // #17629
1389  $this->saveStatus($data);
1390  }
1391 
1395  public function saveStatusAllObject(
1396  array $a_selected = null,
1397  bool $a_redirect = true
1398  ): void {
1399  $user_ids = $this->listed_participants;
1400  $marks = $this->requested_marks;
1401  $notices = $this->requested_tutor_notices;
1402  $status = $this->requested_status;
1403  $filtered_user_ids = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
1404  'edit_submissions_grades',
1405  'edit_submissions_grades',
1406  $this->exercise->getRefId(),
1407  $user_ids
1408  );
1409 
1410  $data = array();
1411  foreach ($filtered_user_ids as $user_id) {
1412  if (is_array($a_selected) &&
1413  !in_array($user_id, $a_selected)) {
1414  continue;
1415  }
1416 
1417  $data[-1][$user_id] = array(
1418  "status" => $status[$user_id] ?? null
1419  );
1420 
1421  if (isset($marks[$user_id])) {
1422  $data[-1][$user_id]["mark"] = $marks[$user_id];
1423  }
1424  if (isset($notices[$user_id])) {
1425  $data[-1][$user_id]["notice"] = $notices[$user_id];
1426  }
1427  }
1428  $this->saveStatus($data, $a_redirect);
1429  }
1430 
1434  public function saveStatusSelectedObject(): void
1435  {
1436  //$members = $this->getMultiActionUserIds();
1437 
1438  if ($this->assignment !== null) {
1439  $this->saveStatusAllObject($this->selected_participants);
1440  } else {
1441  $this->saveStatusParticipantObject($this->selected_ass_ids);
1442  }
1443  }
1444 
1445  // Save status of selected members
1446 
1450  protected function saveStatus(
1451  array $a_data,
1452  bool $a_redirect = true
1453  ): void {
1454  $ilCtrl = $this->ctrl;
1455  $saved_for = array();
1456  foreach ($a_data as $ass_id => $users) {
1457  $ass = ($ass_id < 0)
1458  ? $this->assignment
1459  : new ilExAssignment($ass_id);
1460  foreach ($users as $user_id => $values) {
1461  // this will add team members if available
1462  // $user_id is only the ID of one team member here,
1463  // $sub_user_id will be all team members
1464  $submission = new ilExSubmission($ass, $user_id);
1465  foreach ($submission->getUserIds() as $sub_user_id) {
1466  $uname = ilObjUser::_lookupName($sub_user_id);
1467  $saved_for[$sub_user_id] = $uname["lastname"] . ", " . $uname["firstname"];
1468 
1469  $member_status = $ass->getMemberStatus($sub_user_id);
1470 
1471  // see bug #22566
1472  $status = $values["status"];
1473  if ($status == "") {
1474  $status = self::GRADE_NOT_GRADED;
1475  }
1476  $member_status->setStatus($status);
1477  if (array_key_exists("mark", $values)) {
1478  $member_status->setMark($values["mark"]);
1479  }
1480  if (array_key_exists("notice", $values)) {
1481  $member_status->setNotice($values["notice"]);
1482  }
1483  $member_status->update();
1484  }
1485  }
1486  }
1487 
1488  $save_for_str = "";
1489  if ($saved_for !== []) {
1490  $save_for_str = "(" . implode(" - ", $saved_for) . ")";
1491  }
1492 
1493  if ($a_redirect) {
1494  $this->tpl->setOnScreenMessage('success', $this->lng->txt("exc_status_saved") . " " . $save_for_str, true);
1495  $ilCtrl->redirect($this, $this->getViewBack());
1496  }
1497  }
1498 
1502  public function saveCommentForLearnersObject(): void
1503  {
1504  $res = array("result" => false);
1505 
1506  if ($this->ctrl->isAsynch()) {
1507  $ass_id = $this->requested_ass_id;
1508  $user_id = $this->requested_member_id;
1509  $comment = trim($this->requested_comment);
1510 
1511  if ($ass_id && $user_id) {
1512  $submission = new ilExSubmission($this->assignment, $user_id);
1513  $user_ids = $submission->getUserIds();
1514 
1515  $all_members = new ilExerciseMembers($this->exercise);
1516  $all_members = $all_members->getMembers();
1517 
1518  $reci_ids = array();
1519  foreach ($user_ids as $user_id) {
1520  if (in_array($user_id, $all_members)) {
1521  $member_status = $this->assignment->getMemberStatus($user_id);
1522  $member_status->setComment(ilUtil::stripSlashes($comment));
1523  $member_status->setFeedback(true);
1524  $member_status->update();
1525 
1526  if (trim($comment) !== '' && trim($comment) !== '0') {
1527  $reci_ids[] = $user_id;
1528  }
1529  }
1530  }
1531 
1532  if ($reci_ids !== []) {
1533  // send notification
1534  $this->notification->sendFeedbackNotification(
1535  $ass_id,
1536  $reci_ids,
1537  "",
1538  true
1539  );
1540  }
1541 
1542  $res = array("result" => true, "snippet" => nl2br($comment));
1543  }
1544  }
1545 
1546  echo(json_encode($res));
1547  exit();
1548  }
1549 
1550  public function exportExcelObject(): void
1551  {
1552  $this->exercise->exportGradesExcel();
1553  exit;
1554  }
1555 
1556 
1557  //
1558  // TEAM
1559  //
1560 
1564  public function createTeamsObject(): void
1565  {
1566  $ilCtrl = $this->ctrl;
1567 
1568  $members = $this->getMultiActionUserIds(true);
1569 
1570  $new_members = array();
1571 
1572  foreach ($members as $group) {
1573  if (is_array($group)) {
1574  $new_members = array_merge($new_members, $group);
1575 
1576  $first_user = $group;
1577  $first_user = array_shift($first_user);
1578  $team = ilExAssignmentTeam::getInstanceByUserId($this->assignment->getId(), $first_user);
1579  foreach ($group as $user_id) {
1580  $team->removeTeamMember($user_id);
1581  }
1582  } else {
1583  $new_members[] = $group;
1584  }
1585  }
1586 
1587  if ($new_members !== []) {
1588  // see ilExSubmissionTeamGUI::addTeamMemberActionObject()
1589 
1590  $first_user = array_shift($new_members);
1591  $team = ilExAssignmentTeam::getInstanceByUserId($this->assignment->getId(), $first_user, true);
1592  foreach ($new_members as $user_id) {
1593  $team->addTeamMember($user_id);
1594  }
1595 
1596  // re-evaluate complete team, as some members might have had submitted
1597  $submission = new ilExSubmission($this->assignment, $first_user);
1598  $this->exercise->processExerciseStatus(
1599  $this->assignment,
1600  $team->getMembers(),
1601  $submission->hasSubmitted(),
1602  $submission->validatePeerReviews()
1603  );
1604  }
1605 
1606  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
1607  $ilCtrl->redirect($this, "members");
1608  }
1609 
1613  public function dissolveTeamsObject(): void
1614  {
1615  $ilCtrl = $this->ctrl;
1616 
1617  $members = $this->getMultiActionUserIds(true);
1618 
1619  foreach ($members as $group) {
1620  // if single member - nothing to do
1621  if (is_array($group)) {
1622  // see ilExSubmissionTeamGUI::removeTeamMemberObject()
1623 
1624  $first_user = $group;
1625  $first_user = array_shift($first_user);
1626  $team = ilExAssignmentTeam::getInstanceByUserId($this->assignment->getId(), $first_user);
1627  foreach ($group as $user_id) {
1628  $team->removeTeamMember($user_id);
1629  }
1630 
1631  // reset ex team members, as any submission is not valid without team
1632  $this->exercise->processExerciseStatus(
1633  $this->assignment,
1634  $group,
1635  false
1636  );
1637  }
1638  }
1639 
1640  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
1641  $ilCtrl->redirect($this, "members");
1642  }
1643 
1644  public function adoptTeamsFromGroupObject(
1645  ilPropertyFormGUI $a_form = null
1646  ): void {
1647  $ilCtrl = $this->ctrl;
1648  $ilTabs = $this->tabs_gui;
1649  $lng = $this->lng;
1650  $tpl = $this->tpl;
1651 
1652  $ilTabs->clearTargets();
1653  $ilTabs->setBackTarget(
1654  $lng->txt("back"),
1655  $ilCtrl->getLinkTarget($this, $this->getViewBack())
1656  );
1657 
1658  if ($a_form === null) {
1659  $a_form = $this->initGroupForm();
1660  }
1661  $tpl->setContent($a_form->getHTML());
1662  }
1663 
1664  protected function initGroupForm(): ilPropertyFormGUI
1665  {
1666  $lng = $this->lng;
1667 
1668  $form = new ilPropertyFormGUI();
1669  $form->setTitle($lng->txt("exc_adopt_group_teams") . " - " . $this->assignment->getTitle());
1670  $form->setFormAction($this->ctrl->getFormAction($this, "createTeamsFromGroups"));
1671 
1672  $all_members = array();
1673  foreach (ilExAssignmentTeam::getGroupMembersMap($this->exercise->getRefId()) as $grp_id => $group) {
1674  if (count($group["members"]) !== 0) {
1675  $grp_team = new ilCheckboxGroupInputGUI($lng->txt("obj_grp") . " \"" . $group["title"] . "\"", "grpt[" . $grp_id . "]");
1676  $grp_value = $options = array();
1677  foreach ($group["members"] as $user_id) {
1678  $user_name = ilUserUtil::getNamePresentation($user_id, false, false, "", true);
1679  $options[$user_id] = $user_name;
1680  if (!in_array($user_id, $all_members)) {
1681  $grp_value[] = $user_id;
1682  $all_members[] = $user_id;
1683  }
1684  }
1685  asort($options);
1686  foreach ($options as $user_id => $user_name) {
1687  $grp_team->addOption(new ilCheckboxOption($user_name, $user_id));
1688  }
1689  $grp_team->setValue($grp_value);
1690  } else {
1691  $grp_team = new ilNonEditableValueGUI($group["title"]);
1692  $grp_team->setValue($lng->txt("exc_adopt_group_teams_no_members"));
1693  }
1694  $form->addItem($grp_team);
1695  }
1696 
1697  if ($all_members !== []) {
1698  $form->addCommandButton("createTeamsFromGroups", $lng->txt("save"));
1699  }
1700  $form->addCommandButton("members", $lng->txt("cancel"));
1701 
1702  return $form;
1703  }
1704 
1708  public function createTeamsFromGroupsObject(): void
1709  {
1710  $lng = $this->lng;
1711 
1712  $req_members = $this->requested_group_members;
1713 
1714  $form = $this->initGroupForm();
1715  if ($form->checkInput()) {
1716  $map = ilExAssignmentTeam::getGroupMembersMap($this->exercise->getRefId());
1717  $all_members = $teams = array();
1718  $valid = true;
1719  foreach (array_keys($map) as $grp_id) {
1720  if (isset($req_members[$grp_id]) && is_array($req_members[$grp_id])) {
1721  $members = $req_members[$grp_id];
1722  $teams[] = $members;
1723  $invalid_team_members = array();
1724 
1725  foreach ($members as $user_id) {
1726  if (!array_key_exists($user_id, $all_members)) {
1727  $all_members[$user_id] = $grp_id;
1728  } else {
1729  // user is selected in multiple groups
1730  $invalid_team_members[] = $user_id;
1731  }
1732  }
1733 
1734  if ($invalid_team_members !== []) {
1735  $valid = false;
1736 
1737  $alert = array();
1738  foreach ($invalid_team_members as $user_id) {
1739  $user_name = ilUserUtil::getNamePresentation($user_id, false, false, "", true);
1740  $grp_title = $map[$all_members[$user_id]]["title"];
1741  $alert[] = sprintf($lng->txt("exc_adopt_group_teams_conflict"), $user_name, $grp_title);
1742  }
1743  $input = $form->getItemByPostVar("grpt[" . $grp_id . "]");
1744  $input->setAlert(implode("<br/>", $alert));
1745  }
1746  }
1747  }
1748  if ($valid) {
1749  if ($teams !== []) {
1750  $existing_users = array_keys(ilExAssignmentTeam::getAssignmentTeamMap($this->assignment->getId()));
1751 
1752  // create teams from group selections
1753  $sum = array("added" => 0, "blocked" => 0);
1754  foreach ($teams as $members) {
1755  foreach ($members as $user_id) {
1756  if (!$this->exercise->members_obj->isAssigned($user_id)) {
1757  $this->exercise->members_obj->assignMember($user_id);
1758  }
1759 
1760  if (!in_array($user_id, $existing_users)) {
1761  $sum["added"]++;
1762  } else {
1763  $sum["blocked"]++;
1764  }
1765  }
1766 
1767  $first = array_shift($members);
1768  $team = ilExAssignmentTeam::getInstanceByUserId($this->assignment->getId(), $first, true);
1769 
1770  // getTeamId() does NOT send notification
1771  // $team->sendNotification($this->exercise->getRefId(), $first, "add");
1772 
1773  foreach ($members as $user_id) {
1774  $team->addTeamMember($user_id);
1775  }
1776  }
1777 
1778  $mess = array();
1779  if ($sum["added"] !== 0) {
1780  $mess[] = sprintf($lng->txt("exc_adopt_group_teams_added"), $sum["added"]);
1781  }
1782  if ($sum["blocked"] !== 0) {
1783  $mess[] = sprintf($lng->txt("exc_adopt_group_teams_blocked"), $sum["blocked"]);
1784  }
1785  if ($sum["added"] !== 0) {
1786  $this->tpl->setOnScreenMessage('success', implode(" ", $mess), true);
1787  } else {
1788  $this->tpl->setOnScreenMessage('failure', implode(" ", $mess), true);
1789  }
1790  }
1791  $this->ctrl->redirect($this, "members");
1792  } else {
1793  $this->tpl->setOnScreenMessage('failure', $lng->txt("form_input_not_valid"));
1794  }
1795  }
1796 
1797  $form->setValuesByPost();
1798  $this->adoptTeamsFromGroupObject($form);
1799  }
1800 
1801 
1805 
1806  public function getMultiFeedbackForm(int $a_ass_id): FormAdapterGUI
1807  {
1808  $lng = $this->lng;
1809 
1810  $form = $this->gui->form([self::class], "uploadMultiFeedback")
1811  ->section("main", ilExAssignment::lookupTitle($a_ass_id))
1812  ->file(
1813  "mfzip",
1814  $lng->txt("exc_multi_feedback_file"),
1815  $this->handleMultiFeedbackUploadResult(...),
1816  "rc_id",
1817  "",
1818  1,
1819  ["application/zip"]
1820  );
1821  return $form;
1822  }
1823 
1825  FileUpload $upload,
1826  UploadResult $result
1827  ): BasicHandlerResult {
1828  $feedback_zip = $this->domain->assignment()->tutorFeedbackZip();
1829  $rid = $feedback_zip->importFromUploadResult(
1830  $this->ass_id,
1831  $this->user->getId(),
1832  $result
1833  );
1834  return new \ILIAS\FileUpload\Handler\BasicHandlerResult(
1835  '',
1836  \ILIAS\FileUpload\Handler\HandlerResult::STATUS_OK,
1837  $rid,
1838  ''
1839  );
1840  }
1841 
1842 
1843  public function showMultiFeedbackObject(
1844  FormAdapterGUI $form = null
1845  ): void {
1846  $lng = $this->lng;
1847  $tpl = $this->tpl;
1848 
1849  $this->tpl->setOnScreenMessage('info', $lng->txt("exc_multi_feedb_info"));
1850 
1851  $this->addSubTabs("assignment");
1852 
1853  // #13719
1854  $this->gui->button(
1855  $this->lng->txt("exc_download_zip_structure"),
1856  $this->ctrl->getLinkTarget($this, "downloadMultiFeedbackZip")
1857  )->toToolbar();
1858 
1859  if ($form === null) {
1860  $form = $this->getMultiFeedbackForm($this->assignment->getId());
1861  }
1862 
1863  $tpl->setContent($form->render());
1864  }
1865 
1869  public function downloadMultiFeedbackZipObject(): void
1870  {
1871  $st_file = $this->domain->assignment()->tutorFeedbackZip()->getMultiFeedbackStructureFile(
1872  $this->assignment,
1873  $this->exercise
1874  );
1875  $this->gui->httpUtil()->deliverString(
1876  $st_file->content,
1877  $st_file->filename,
1878  "application/zip"
1879  );
1880  }
1881 
1885  public function uploadMultiFeedbackObject(): void
1886  {
1887  // #11983
1888  $form = $this->getMultiFeedbackForm($this->assignment->getId());
1889  if ($form->isValid()) {
1890  $this->ctrl->redirect($this, "showMultiFeedbackConfirmationTable");
1891  }
1892 
1893  $this->showMultiFeedbackObject($form);
1894  }
1895 
1900  {
1901  $tpl = $this->tpl;
1902 
1903  $this->addSubTabs("assignment");
1904 
1905  $tab = new ilFeedbackConfirmationTable2GUI($this, "showMultiFeedbackConfirmationTable", $this->assignment);
1906  $tpl->setContent($tab->getHTML());
1907  }
1908 
1912  public function cancelMultiFeedbackObject(): void
1913  {
1914  $this->ctrl->redirect($this, "members");
1915  }
1916 
1920  public function saveMultiFeedbackObject(): void
1921  {
1922  $feedback_zip = $this->domain->assignment()->tutorFeedbackZip();
1923  $feedback_zip->saveMultiFeedbackFiles(
1924  $this->exercise,
1925  $this->assignment->getId(),
1926  $this->user->getId(),
1928  );
1929 
1930  $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
1931  $this->ctrl->redirect($this, "members");
1932  }
1933 
1934 
1935  //
1936  // individual deadlines
1937  //
1938 
1939  protected function initIndividualDeadlineModal(): string
1940  {
1941  $lng = $this->lng;
1942  $tpl = $this->tpl;
1943 
1944  // prepare modal+
1945  $modal = ilModalGUI::getInstance();
1946  $modal->setHeading($lng->txt("exc_individual_deadline"));
1947  $modal->setId("ilExcIDl");
1948  $modal->setBody('<div id="ilExcIDlBody"></div>');
1949  $modal = $modal->getHTML();
1950 
1951  $ajax_url = $this->ctrl->getLinkTarget($this, "handleIndividualDeadlineCalls", "", true, false);
1952 
1953  $tpl->addJavaScript("./Modules/Exercise/js/ilExcIDl.js", true, 3);
1954  $tpl->addOnLoadCode('il.ExcIDl.init("' . $ajax_url . '");');
1955 
1957 
1958  return $modal;
1959  }
1960 
1964  protected function parseIndividualDeadlineData(
1965  array $a_data
1966  ): array {
1967  if ($a_data) {
1968  $map = array();
1969  $ass_tmp = array();
1970  foreach ($a_data as $item) {
1971  $item = explode("_", $item);
1972  $ass_id = $item[0];
1973  $user_id = $item[1];
1974 
1975  if (!array_key_exists($ass_id, $ass_tmp)) {
1976  if ($this->assignment &&
1977  $ass_id == $this->assignment->getId()) {
1978  $ass_tmp[$ass_id] = $this->assignment;
1979  } else {
1980  $ass_tmp[$ass_id] = new ilExAssignment($ass_id);
1981  }
1982  }
1983 
1984  $map[$ass_id][] = $user_id;
1985  }
1986 
1987  return array($map, $ass_tmp);
1988  }
1989  return [];
1990  }
1991 
1996  protected function handleIndividualDeadlineCallsObject(): void
1997  {
1998  $tpl = $this->tpl;
1999 
2000  $this->ctrl->saveParameter($this, "part_id");
2001 
2002  // from request "dn", see ilExcIdl.js
2003  if ($this->done) {
2004  $this->tpl->setOnScreenMessage('success', $this->lng->txt("settings_saved"), true);
2005  $this->ctrl->redirect($this, $this->assignment !== null
2006  ? "members"
2007  : "showParticipant");
2008  }
2009 
2010  // initial form call
2011  if ($this->requested_idl_id != "") {
2012  $tmp = $this->parseIndividualDeadlineData(explode(",", $this->requested_idl_id));
2013  if (is_array($tmp)) {
2014  $form = $this->initIndividualDeadlineForm($tmp[1], $tmp[0]);
2015  echo $form->getHTML() .
2016  $tpl->getOnLoadCodeForAsynch();
2017  }
2018  }
2019  // form "submit"
2020  else {
2021  $tmp = array();
2022  $post = $this->http->request()->getParsedBody();
2023  foreach (array_keys($post) as $id) {
2024  if (substr($id, 0, 3) == "dl_") {
2025  $tmp[] = substr($id, 3);
2026  }
2027  }
2028  $tmp = $this->parseIndividualDeadlineData($tmp);
2029  $ass_map = $tmp[1];
2030  $users = $tmp[0];
2031  unset($tmp);
2032 
2033  $form = $this->initIndividualDeadlineForm($ass_map, $users);
2034  $res = array();
2035  if ($valid = $form->checkInput()) {
2036  foreach ($users as $ass_id => $users2) {
2037  $ass = $ass_map[$ass_id];
2038 
2039  // :TODO: should individual deadlines BEFORE extended be possible?
2040  $dl = new ilDateTime($ass->getDeadline(), IL_CAL_UNIX);
2041 
2042  foreach ($users2 as $user_id) {
2043  $date_field = $form->getItemByPostVar("dl_" . $ass_id . "_" . $user_id);
2044  if (ilDate::_before($date_field->getDate(), $dl)) {
2045  $date_field->setAlert(sprintf($this->lng->txt("exc_individual_deadline_before_global"), ilDatePresentation::formatDate($dl)));
2046  $valid = false;
2047  } else {
2048  $res[$ass_id][$user_id] = $date_field->getDate();
2049  }
2050  }
2051  }
2052  }
2053 
2054  if (!$valid) {
2055  $form->setValuesByPost();
2056  echo $form->getHTML() .
2057  $tpl->getOnLoadCodeForAsynch();
2058  } else {
2059  foreach ($res as $ass_id => $users) {
2060  $ass = $ass_map[$ass_id];
2061 
2062  foreach ($users as $id => $date) {
2063  $ass->setIndividualDeadline($id, $date);
2064  if (is_numeric($id)) {
2065  $this->notification->sendDeadlineSetNotification($ass_id, $id);
2066  }
2067  }
2068 
2069  $ass->recalculateLateSubmissions();
2070  }
2071 
2072  echo "ok";
2073  }
2074  }
2075 
2076  exit();
2077  }
2078 
2082  protected function initIndividualDeadlineForm(
2083  array $a_ass_map,
2084  array $ids
2085  ): ilPropertyFormGUI {
2086  $form = new ilPropertyFormGUI();
2087  $form->setFormAction($this->ctrl->getFormAction($this));
2088  $form->setName("ilExcIDlForm");
2089 
2090  foreach ($ids as $ass_id => $users) {
2091  $ass = $a_ass_map[$ass_id];
2092 
2093  $section = new ilFormSectionHeaderGUI();
2094  $section->setTitle($ass->getTitle());
2095  $form->addItem($section);
2096 
2097  $teams = ilExAssignmentTeam::getInstancesFromMap($ass->getId());
2098 
2099  $values = $ass->getIndividualDeadlines();
2100 
2101  foreach ($users as $id) {
2102  // single user
2103  if (is_numeric($id)) {
2104  $name = ilObjUser::_lookupName($id);
2105  $name = $name["lastname"] . ", " . $name["firstname"];
2106  }
2107  // team
2108  else {
2109  $name = "";
2110  $team_id = (int) substr($id, 1);
2111  if (array_key_exists($team_id, $teams)) {
2112  $name = array();
2113  foreach ($teams[$team_id]->getMembers() as $member_id) {
2114  $uname = ilObjUser::_lookupName($member_id);
2115  $name[] = $uname["lastname"] . ", " . $uname["firstname"];
2116  }
2117  asort($name);
2118  $name = implode("<br />", $name);
2119  }
2120  }
2121 
2122  $dl = new ilDateTimeInputGUI($name, "dl_" . $ass_id . "_" . $id);
2123  $dl->setShowTime(true);
2124  $dl->setRequired(true);
2125  $form->addItem($dl);
2126 
2127  if (array_key_exists($id, $values)) {
2128  $dl->setDate(new ilDateTime($values[$id], IL_CAL_UNIX));
2129  }
2130  }
2131  }
2132 
2133  $form->addCommandButton("", $this->lng->txt("save"));
2134 
2135  return $form;
2136  }
2137 
2141  protected function setIndividualDeadlineObject(): void
2142  {
2143  // this will only get called if no selection
2144  $this->tpl->setOnScreenMessage('failure', $this->lng->txt("select_one"));
2145 
2146  if ($this->assignment !== null) {
2147  $this->membersObject();
2148  } else {
2149  $this->showParticipantObject();
2150  }
2151  }
2152 
2153  public function initFilter(): void
2154  {
2155  if ($this->requested_filter_status != "") {
2156  $this->filter["status"] = $this->requested_filter_status;
2157  }
2158 
2159  $this->lng->loadLanguageModule("search");
2160 
2161  $this->toolbar->setFormAction($this->ctrl->getFormAction($this, "listTextAssignment"));
2162 
2163  // Status
2164 
2165  $si_status = new ilSelectInputGUI($this->lng->txt("exc_tbl_status"), "filter_status");
2166  $options = array(
2167  "" => $this->lng->txt("search_any"),
2168  self::GRADE_NOT_GRADED => $this->lng->txt("exc_notgraded"),
2169  self::GRADE_PASSED => $this->lng->txt("exc_passed"),
2170  self::GRADE_FAILED => $this->lng->txt("exc_failed")
2171  );
2172  $si_status->setOptions($options);
2173  $si_status->setValue($this->filter["status"] ?? "");
2174 
2175  $si_feedback = new ilSelectInputGUI($this->lng->txt("feedback"), "filter_feedback");
2176  $options = array(
2177  self::FEEDBACK_FULL_SUBMISSION => $this->lng->txt("submissions_feedback"),
2178  self::FEEDBACK_ONLY_SUBMISSION => $this->lng->txt("submissions_only")
2179  );
2180  $si_feedback->setOptions($options);
2181  $si_feedback->setValue($this->filter["feedback"] ?? "");
2182 
2183  $this->toolbar->addInputItem($si_status, true);
2184 
2185  // Submissions and Feedback
2186  #24713
2187  if ($this->assignment->getPeerReview()) {
2188  if ($this->requested_filter_feedback != "") {
2189  $this->filter["feedback"] = $this->requested_filter_feedback;
2190  } else {
2191  $this->filter["feedback"] = "submission_feedback";
2192  }
2193 
2194  $si_feedback = new ilSelectInputGUI($this->lng->txt("feedback"), "filter_feedback");
2195  $options = array(
2196  "submission_feedback" => $this->lng->txt("submissions_feedback"),
2197  "submission_only" => $this->lng->txt("submissions_only")
2198  );
2199  $si_feedback->setOptions($options);
2200  $si_feedback->setValue($this->filter["feedback"] ?? "");
2201 
2202  $this->toolbar->addInputItem($si_feedback, true);
2203  }
2204 
2205  $this->gui->button(
2206  $this->lng->txt("filter"),
2207  "listTextAssignment"
2208  )->submit()->toToolbar();
2209  }
2210 
2214  public function openSubmissionPrintViewObject(): void
2215  {
2216  $this->openSubmissionViewObject(true);
2217  }
2218 
2222  public function openSubmissionViewObject(bool $print_version = false): void
2223  {
2224  global $DIC;
2225 
2226  $member_id = $this->requested_member_id;
2227 
2228  $submission = new ilExSubmission($this->assignment, $member_id);
2229 
2230  $last_opening = $submission->getLastOpeningHTMLView();
2231  $submission_time = $submission->getLastSubmission();
2232 
2233  // e.g. /<datadir>/<clientid>/ilExercise/3/exc_367/subm_1/<ass_id>/20210628175716_368
2234  $zip_original_full_path = $this->getSubmissionZipFilePath($submission, $print_version);
2235  $this->log->debug("zip original full path: " . $zip_original_full_path);
2236 
2237  // e.g. ilExercise/3/exc_367/subm_1/<ass_id>/20210628175716_368
2238  $zip_internal_path = $this->getWebFilePathFromExternalFilePath($zip_original_full_path);
2239  $this->log->debug("zip internal path: " . $zip_internal_path);
2240 
2241  $arr = explode("_", basename($zip_original_full_path));
2242  $obj_date = $arr[0];
2243  $obj_id = (int) ($arr[1] ?? 0);
2244  if ($obj_id === 0) {
2245  throw new ilExerciseException("Cannot open HTML view for " . $zip_internal_path . " / " .
2246  $submission->getSubmittedPrintFile() . ".");
2247  }
2248 
2249  $obj_id = $this->assignment->getAssignmentType()->getExportObjIdForResourceId($obj_id);
2250  if ($print_version) {
2251  $obj_id .= "print";
2252  }
2253 
2254  $obj_dir = $this->assignment->getAssignmentType()->getStringIdentifier() . "_" . $obj_id;
2255 
2256  $index_html_file = ILIAS_WEB_DIR .
2257  DIRECTORY_SEPARATOR .
2258  CLIENT_ID .
2259  DIRECTORY_SEPARATOR .
2260  dirname($zip_internal_path) .
2261  DIRECTORY_SEPARATOR .
2262  $obj_dir .
2263  DIRECTORY_SEPARATOR .
2264  "index.html";
2265  $this->log->debug("index html file: " . $index_html_file);
2266 
2267  $web_filesystem = $DIC->filesystem()->web();
2268  if ($last_opening > $submission_time && $web_filesystem->has($index_html_file)) {
2269  ilWACSignedPath::signFolderOfStartFile($index_html_file);
2270  ilUtil::redirect($index_html_file);
2271  }
2272  $error_msg = "";
2273  if ($zip_original_full_path) {
2274  $file_copied = $this->copyFileToWebDir($zip_internal_path);
2275  $this->log->debug("file copied: " . $file_copied);
2276  // e.g. data/ilias/ilExercise/3/exc_327/subm_9/2/20231212085734_167.zip ?
2277  if ($file_copied) {
2278  $this->zip->unzipFile($file_copied);
2279  $web_filesystem->delete($zip_internal_path);
2280  $this->log->debug("deleting: " . $zip_internal_path);
2281 
2282  $submission_repository = $this->service->repo()->submission();
2283  $submission_repository->updateWebDirAccessTime($this->assignment->getId(), $member_id);
2284  ilWACSignedPath::signFolderOfStartFile($index_html_file);
2285  ilUtil::redirect($index_html_file . "?" . time());
2286  }
2287 
2288  $error_msg = $this->lng->txt("exc_copy_zip_error");
2289  }
2290 
2291  if ($error_msg === '' || $error_msg === '0') {
2292  $error_msg = $this->lng->txt("exc_find_zip_error");
2293  }
2294 
2295  $this->tpl->setOnScreenMessage('failure', $error_msg);
2296  }
2297 
2303  protected function getSubmissionZipFilePath(
2304  ilExSubmission $submission,
2305  bool $print_versions = false
2306  ): ?string {
2307  $submitted = $submission->getFiles(
2308  null,
2309  false,
2310  null,
2311  $print_versions
2312  );
2313  if ($submitted !== []) {
2314  $submitted = array_pop($submitted);
2315 
2316  return $submitted['filename'];
2317  }
2318 
2319  return null;
2320  }
2321 
2326  protected function copyFileToWebDir(
2327  string $internal_file_path
2328  ): ?string {
2329  global $DIC;
2330 
2331  $web_filesystem = $DIC->filesystem()->web();
2332  $data_filesystem = $DIC->filesystem()->storage();
2333 
2334  $internal_dirs = dirname($internal_file_path);
2335  $zip_file = basename($internal_file_path);
2336 
2337  if ($data_filesystem->has($internal_file_path)) {
2338  $this->log->debug("internal file path: " . $internal_file_path);
2339  if ($web_filesystem->hasDir($internal_dirs)) {
2340  $web_filesystem->deleteDir($internal_dirs);
2341  }
2342  $web_filesystem->createDir($internal_dirs);
2343 
2344  if ($web_filesystem->has($internal_file_path)) {
2345  $web_filesystem->delete($internal_file_path);
2346  }
2347  if (!$web_filesystem->has($internal_file_path)) {
2348  $this->log->debug("writing: " . $internal_file_path);
2349  $stream = $data_filesystem->readStream($internal_file_path);
2350  $web_filesystem->writeStream($internal_file_path, $stream);
2351 
2352  return ILIAS_ABSOLUTE_PATH .
2353  DIRECTORY_SEPARATOR .
2354  ILIAS_WEB_DIR .
2355  DIRECTORY_SEPARATOR .
2356  CLIENT_ID .
2357  DIRECTORY_SEPARATOR .
2358  $internal_dirs .
2359  DIRECTORY_SEPARATOR .
2360  $zip_file;
2361  }
2362  }
2363 
2364  return null;
2365  }
2366 
2371  string $external_file_path
2372  ): string {
2373  list($external_path, $internal_file_path) = explode(CLIENT_ID . "/ilExercise", $external_file_path);
2374  $internal_file_path = "ilExercise" . $internal_file_path;
2375  return $internal_file_path;
2376  }
2377 
2378  /*
2379  * Add the Back link to the tabs. (used in submission list and submission compare)
2380  */
2381  protected function setBackToMembers(): void
2382  {
2383  //tabs
2384  $this->tabs_gui->clearTargets();
2385  $this->tabs_gui->setBackTarget(
2386  $this->lng->txt("back"),
2387  $this->ctrl->getLinkTarget($this, "members")
2388  );
2389  }
2390 
2392  array $a_data
2393  ): array {
2394  $user = new ilObjUser($a_data["user_id"]);
2395  $uname = $user->getFirstname() . " " . $user->getLastname();
2396 
2397  $data = array(
2398  "uid" => $a_data["user_id"],
2399  "uname" => $uname,
2400  "udate" => $a_data["ts"],
2401  "utext" => ilRTE::_replaceMediaObjectImageSrc($a_data["atext"], 1) // mob id to mob src
2402  );
2403 
2404  //get data peer and assign it
2405  $peer_review = new ilExPeerReview($this->assignment);
2406  $data["peer"] = array();
2407  foreach ($peer_review->getPeerReviewsByPeerId($a_data['user_id']) as $value) {
2408  $data["peer"][] = $value['giver_id'];
2409  }
2410 
2411  $data["fb_received"] = count($data["peer"]);
2412  $data["fb_given"] = $peer_review->countGivenFeedback(true, $a_data["user_id"]);
2413 
2414  return $data;
2415  }
2416 
2417  public function sendGradingNotificationObject(): void
2418  {
2419 
2420  $ass_id = $this->request->getAssId();
2421  $selected_users = $this->request->getSelectedParticipants();
2422 
2423  $graded_users = array_filter($selected_users, function ($user_id) {
2424  return $this->assignment->getMemberStatus($user_id)->getStatus() !== "notgraded";
2425  });
2426 
2427  if (count($graded_users) === 0) {
2428  $this->tpl->setOnScreenMessage("failure", $this->lng->txt("exc_no_graded_mem_selected"), true);
2429  $this->ctrl->redirect($this, $this->getViewBack());
2430  }
2431 
2432  $not = new ilExerciseMailNotification();
2434  $not->setAssignmentId($ass_id);
2435  $not->setObjId($this->exercise->getId());
2436  $not->setRefId($this->exercise->getRefId());
2437  $not->setRecipients($graded_users);
2438  $not->send();
2439  $this->tpl->setOnScreenMessage("success", $this->lng->txt("exc_graded_mem_notified"), true);
2440  $this->ctrl->redirect($this, $this->getViewBack());
2441  }
2442 
2443  protected function setFailedObject(): void
2444  {
2445  $members = $this->getMultiActionUserIds();
2446  $done = false;
2447  if ($this->assignment !== null) {
2448  foreach (array_keys($members) as $mem) {
2449  $done = true;
2450  $this->setSingleStatus($this->assignment->getId(), $mem, "failed");
2451  }
2452  if ($done) {
2453  $this->tpl->setOnScreenMessage("success", $this->lng->txt("msg_obj_modified"), true);
2454  }
2455  }
2456  $this->ctrl->redirect($this, "members");
2457  }
2458 
2459  protected function setPassedObject(): void
2460  {
2461  $members = $this->getMultiActionUserIds();
2462  $done = false;
2463  if ($this->assignment !== null) {
2464  foreach (array_keys($members) as $mem) {
2465  $done = true;
2466  $this->setSingleStatus($this->assignment->getId(), $mem, "passed");
2467  }
2468  if ($done) {
2469  $this->tpl->setOnScreenMessage("success", $this->lng->txt("msg_obj_modified"), true);
2470  }
2471  }
2472  $this->ctrl->redirect($this, "members");
2473  }
2474 
2475  protected function setSingleStatus($ass_id, $part_id, $status): void
2476  {
2477  $ass = new ilExAssignment($ass_id);
2478  $submission = new ilExSubmission($ass, $part_id);
2479  $member_status = $ass->getMemberStatus($part_id);
2480  $member_status->setStatus($status);
2481  $member_status->update();
2482  }
2483 }
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
An entity that renders components to a string output.
Definition: Renderer.php:30
showMultiFeedbackObject(FormAdapterGUI $form=null)
__construct(InternalService $service, ilExAssignment $a_ass=null)
Constructor.
$res
Definition: ltiservices.php:69
Exercise UI frontend presentation service class.
getFiles(array $a_file_ids=null, bool $a_only_valid=false, string $a_min_timestamp=null, bool $print_versions=false)
Get submission items (not only files)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
exit
Definition: login.php:29
Exercise assignment.
const IL_CAL_DATETIME
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static downloadAllAssignmentFiles(ilExAssignment $a_ass, array $members, string $to_path)
Download all submitted files of an assignment (all user)
Class ilExerciseMembers.
handleMultiFeedbackUploadResult(FileUpload $upload, UploadResult $result)
This class represents a selection list property in a property form.
TutorFeedbackFileManager $tutor_feedback_file
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
txt(string $a_topic, string $a_default_lang_fallback_mod="")
gets the text for a given topic if the topic is not in the list, the topic itself with "-" will be re...
redirect(object $a_gui_obj, string $a_cmd=null, string $a_anchor=null, bool $is_async=false)
static _before(ilDateTime $start, ilDateTime $end, string $a_compare_field='', string $a_tz='')
compare two dates and check start is before end This method does not consider tz offsets.
setOnScreenMessage(string $type, string $a_txt, bool $a_keep=false)
Set a message to be displayed to the user.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
ILIAS Exercise Notification NotificationManager $notification
Class ChatMainBarProvider .
getTutorNotices()
key might be ass_ids or user_ids!
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
$valid
copyFileToWebDir(string $internal_file_path)
Generate the directories and copy the file if necessary.
static lookupTitle(int $a_id)
getStatus()
key might be ass_ids or user_ids!
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false)
getMarks()
key might be ass_ids or user_ids!
static _lookupName(int $a_user_id)
lookup user name
Exercise internal service.
getWebFilePathFromExternalFilePath(string $external_file_path)
Get the object specific file path from an external full file path.
static _lookupId($a_user_str)
setParameterByClass(string $a_class, string $a_parameter, $a_value)
request(?array $query_params=null, ?array $post_data=null)
Get request wrapper.
setOptions(array $a_options)
static getInstanceByUserId(int $a_assignment_id, int $a_user_id, bool $a_create_on_demand=false)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const IL_CAL_UNIX
setSingleStatus($ass_id, $part_id, $status)
filterParticipantsByAccess()
Filter manageable members by position or rbac access.
static getAssignmentParticipants(int $a_exercise_id, int $a_ass_id)
This class represents a date/time property in a property form.
static getAdoptableGroups(int $a_exc_ref_id)
getSubmissionZipFilePath(ilExSubmission $submission, bool $print_versions=false)
Returns the ZIP file path from outside web directory.
global $DIC
Definition: feed.php:28
saveStatusAllObject(array $a_selected=null, bool $a_redirect=true)
saveStatusParticipantObject(array $selected_ass_ids=null)
Save assignment status (participant view)
resetOffset(bool $a_in_determination=false)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Exercise peer review.
filterUserIdsByRbacOrPositionOfCurrentUser(string $rbac_perm, string $pos_perm, int $ref_id, array $user_ids)
compareTextAssignmentsObject()
TODO -> Deal with the redirection after update the grade via action button.
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
Class ilObjExercise.
$ref_id
Definition: ltiauth.php:67
static http()
Fetches the global http state from ILIAS.
static getNamePresentation( $a_user_id, bool $a_user_image=false, bool $a_profile_link=false, string $a_profile_back_link='', bool $a_force_first_lastname=false, bool $a_omit_login=false, bool $a_sortable=true, bool $a_return_data_array=false, $a_ctrl_path='ilpublicuserprofilegui')
Default behaviour is:
setContent(string $a_html)
Sets content for standard template.
$GLOBALS["DIC"]
Definition: wac.php:31
static signFolderOfStartFile(string $start_file_path)
Download submissions and feedback for exercises.
downloadSubmissionsObject(?array $selected_participants=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
uploadMultiFeedbackObject()
Upload multi feedback file.
const CLIENT_ID
Definition: constants.php:41
string $key
Consumer key/client ID value.
Definition: System.php:193
downloadMultiFeedbackZipObject()
Download multi-feedback structrue file.
$url
Definition: ltiregstart.php:35
static fillAutoCompleteToolbar(object $parent_object, ilToolbarGUI $toolbar=null, array $a_options=[], bool $a_sticky=false)
array( auto_complete_name = $lng->txt(&#39;user&#39;), auto_complete_size = 15, user_type = array(ilCoursePar...
addJavaScript(string $a_js_file, bool $a_add_version_parameter=true, int $a_batch=2)
Add a javascript file that should be included in the header.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class FileUpload.
Definition: FileUpload.php:34
addOnLoadCode(string $a_code, int $a_batch=2)
Add on load code.
static getAssignmentFilesByUsers(int $a_exc_id, int $a_ass_id, array $a_users)
$comment
Definition: buildRTE.php:72
static getAssignmentTeamMap(int $a_ass_id)
static redirect(string $a_script)
ilExerciseManagementGUI: ilFileSystemGUI, ilRepositorySearchGUI ilExerciseManagementGUI: ilExSubmiss...
static getInstancesByExercise(int $a_exc_id)
static getInstanceByObjId(?int $obj_id, bool $stop_on_error=true)
get an instance of an Ilias object by object id
static getInstance()
final const SIGNATURE_KEY
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
cancelMultiFeedbackObject()
Cancel Multi Feedback.
openSubmissionViewObject(bool $print_version=false)
Open HTML view for portfolio submissions.
membersObject()
All participants and submission of one assignment.
This class represents a text area property in a property form.
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static getRedirectTarget( $gui, string $cmd, array $gui_params=[], array $mail_params=[], array $context_params=[])
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
adoptTeamsFromGroupObject(ilPropertyFormGUI $a_form=null)
showMultiFeedbackConfirmationTableObject()
Show multi feedback confirmation table.
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...
getOnLoadCodeForAsynch()
Get js onload code for ajax calls.
saveMultiFeedbackObject()
Save multi feedback.
File System Explorer GUI class.
filter(string $filter_id, $class_path, string $cmd, bool $activated=true, bool $expanded=true)
setParameter(object $a_gui_obj, string $a_parameter, $a_value)
static getGroupMembersMap(int $a_exc_ref_id)
static _lookupType(int $id, bool $reference=false)
$post
Definition: ltitoken.php:49
Exercise gui request wrapper.
Exercise participant table.
getMultiActionUserIds(bool $a_keep_teams=false)
ILIAS Exercise InternalDomainService $domain
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...
initIndividualDeadlineForm(array $a_ass_map, array $ids)
saveStatus(array $a_data, bool $a_redirect=true)
openSubmissionPrintViewObject()
Open HTML view for portfolio submissions.
static getInstancesFromMap(int $a_assignment_id)
const ILIAS_WEB_DIR
Definition: constants.php:45
static sortArray(array $array, string $a_array_sortby_key, string $a_array_sortorder="asc", bool $a_numeric=false, bool $a_keep_keys=false)
setTableId(string $a_val)
static _lookupLogin(int $a_user_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...