ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilExerciseManagementCollectFilesJob.php
Go to the documentation of this file.
1 <?php
2 
26 
31 {
32  public const FBK_DIRECTORY = "Feedback_files";
33  public const LINK_COLOR = "0,0,255";
34  public const BG_COLOR = "255,255,255";
35  //Column number incremented in ilExcel
36  public const PARTICIPANT_LASTNAME_COLUMN = 0;
37  public const PARTICIPANT_FIRSTNAME_COLUMN = 1;
38  public const PARTICIPANT_LOGIN_COLUMN = 2;
39  public const SUBMISSION_DATE_COLUMN = 3;
40  public const FIRST_DEFAULT_SUBMIT_COLUMN = 4;
41  public const FIRST_DEFAULT_REVIEW_COLUMN = 5;
42  protected \ILIAS\Exercise\Team\TeamManager $team;
43 
44  protected ilLogger $logger;
45  protected string $target_directory = "";
46  protected string $submissions_directory = "";
48  protected int $user_id = 0;
49  protected int $exercise_id = 0;
50  protected int $exercise_ref_id = 0;
51  protected ?string $temp_dir = null;
52  protected ilLanguage $lng;
53  protected string $sanitized_title = ""; //sanitized file name/sheet title
54  protected ilExcel $excel;
55  protected array $criteria_items = [];
56  protected array $title_columns = [];
57  protected array $ass_types_with_files = []; //TODO will be deprecated when use the new assignment type interface
58  protected int $participant_id = 0;
59  protected ?array $selected_participants = null;
60 
64  public function __construct()
65  {
66  global $DIC;
67  $this->lng = $DIC->language();
68  $this->lng->loadLanguageModule('exc');
69  //TODO will be deprecated when use the new assignment type interface
70  $this->ass_types_with_files = array(
75  );
77  $this->logger = $DIC->logger()->exc();
78  $this->team = $DIC->exercise()->internal()->domain()->team();
79  }
80 
84  public function getInputTypes(): array
85  {
86  return
87  [
88  new SingleType(IntegerValue::class),
89  new SingleType(IntegerValue::class),
90  new SingleType(IntegerValue::class),
91  new SingleType(IntegerValue::class),
92  new SingleType(IntegerValue::class),
93  new SingleType(StringValue::class),
94  ];
95  }
96 
97  public function getOutputType(): Type
98  {
99  return new SingleType(StringValue::class);
100  }
101 
102  public function isStateless(): bool
103  {
104  return true;
105  }
106 
115  public function run(
116  array $input,
117  Observer $observer
118  ): Value {
119  $this->exercise_id = $input[0]->getValue();
120  $this->exercise_ref_id = $input[1]->getValue();
121  $assignment_id = $input[2]->getValue();
122  $participant_id = $input[3]->getValue();
123  $this->user_id = $input[4]->getValue();
124  $selected_participants = $input[5]->getValue();
125  $this->logger->debug("Collect files. assignment id: " . $assignment_id . ", selected participants: " . $selected_participants);
126  if (trim($selected_participants) === "") {
127  $this->selected_participants = null;
128  } else {
129  $this->selected_participants = explode(",", $selected_participants);
130  }
131  $final_directory = "";
132 
133  //if we have assignment
134  if ($assignment_id > 0) {
135  $this->collectAssignmentData($assignment_id);
136  $final_directory = $this->target_directory;
137  }
138 
139  if ($participant_id > 0) {
140  $this->participant_id = $participant_id;
141  $assignments = ilExAssignment::getInstancesByExercise($this->exercise_id);
142  foreach ($assignments as $assignment) {
143  $this->collectAssignmentData($assignment->getId());
144  }
145  $final_directory = $this->temp_dir . DIRECTORY_SEPARATOR . ilExSubmission::getDirectoryNameFromUserData($participant_id);
146  }
147 
148  $out = new StringValue();
149  $out->setValue($final_directory);
150  return $out;
151  }
152 
157  public function copyFileToSubDirectory(string $a_directory, string $a_file): void
158  {
159  $dir = $this->target_directory . "/" . $a_directory;
160 
161  if (!is_dir($dir)) {
163  }
164 
165  copy($a_file, $dir . "/" . basename($a_file));
166 
167  /*global $DIC;
168  $fs = $DIC->filesystem();
169 
170  $fs->storage()->copy($a_file, $this->temp_dir."/".basename($a_file));*/
171  }
172 
174  {
175  return 30;
176  }
177 
182  protected function addColumnTitles(): void
183  {
184  $col = 0;
185  foreach ($this->title_columns as $title) {
186  $this->excel->setCell(1, $col, $title);
187  $col++;
188  }
189  }
190 
195  protected function createUniqueTempDirectory(): void
196  {
197  $this->temp_dir = ilFileUtils::ilTempnam();
198  ilFileUtils::makeDirParents($this->temp_dir);
199  }
200 
204  protected function createTargetDirectory(): void
205  {
206  $path = $this->temp_dir . DIRECTORY_SEPARATOR;
207  if ($this->participant_id > 0) {
208  $user_dir = ilExSubmission::getDirectoryNameFromUserData($this->participant_id);
209  $path .= $user_dir . DIRECTORY_SEPARATOR;
210  }
211  $this->target_directory = $path . $this->sanitized_title;
212  ilFileUtils::makeDirParents($this->target_directory);
213  }
214 
218  protected function createSubmissionsDirectory(): void
219  {
220  $this->logger->dump("lang key => " . $this->lng->getLangKey());
221  $this->submissions_directory = $this->target_directory . DIRECTORY_SEPARATOR . $this->lng->txt("exc_ass_submission_zip");
222  ilFileUtils::createDirectory($this->submissions_directory);
223  }
224 
233  public function collectSubmissionFiles(): void
234  {
235  $members = array();
236 
237  $exercise = new ilObjExercise($this->exercise_id, false);
238 
239  if ($this->participant_id > 0) {
240  $exc_members_id = array($this->participant_id);
241  } else {
242  $exc_members_id = $exercise->members_obj->getMembers();
243  }
244 
245  $filter = new ilExerciseMembersFilter($this->exercise_ref_id, $exc_members_id, $this->user_id);
246  $exc_members_id = $filter->filterParticipantsByAccess();
247 
248  foreach ($exc_members_id as $member_id) {
249  if (!is_null($this->selected_participants) && !in_array($member_id, $this->selected_participants)) {
250  continue;
251  }
252  $submission = new ilExSubmission($this->assignment, $member_id);
253  $submission->updateTutorDownloadTime();
254 
255  // get member object (ilObjUser)
256  if (ilObject::_exists($member_id)) {
257  // adding file metadata
258  foreach ($submission->getFiles() as $file) {
259  $members[$file["user_id"]]["files"][$file["returned_id"]] = $file;
260  }
261 
263  $tmp_obj = ilObjectFactory::getInstanceByObjId($member_id);
264  $members[$member_id]["name"] = $tmp_obj->getFirstname() . " " . $tmp_obj->getLastname();
265  unset($tmp_obj);
266  }
267  }
268  ilExSubmission::downloadAllAssignmentFiles($this->assignment, $members, $this->submissions_directory);
269  }
270 
271  protected function isExcelNeeded(int $a_ass_type, bool $a_has_fbk): bool
272  {
273  if ($a_ass_type == ilExAssignment::TYPE_TEXT || $a_ass_type == ilExAssignment::TYPE_UPLOAD
274  || $a_ass_type == ilExAssignment::TYPE_UPLOAD_TEAM) {
275  return true;
276  } elseif ($a_has_fbk && $a_ass_type != ilExAssignment::TYPE_UPLOAD_TEAM) {
277  return true;
278  }
279  return false;
280  }
281 
286  protected function addCriteriaToExcel(
287  int $feedback_giver,
288  int $participant_id,
289  int $row,
290  int $col
291  ): void {
292  $submission = new ilExSubmission($this->assignment, $participant_id);
293 
294  //Possible TODO: This getPeerReviewValues doesn't return always the same array structure then the client classes have
295  //to deal with this. Use only one data structure will avoid this extra work.
296  //values can be [19] => "blablablab" or ["text"] => "blablabla"
297  $values = $submission->getPeerReview()->getPeerReviewValues($feedback_giver, $participant_id);
298 
299  foreach ($this->criteria_items as $item) {
300  $col++;
301 
302  //Criteria without catalog doesn't have ID nor TITLE. The criteria instance is given via "type" ilExcCriteria::getInstanceByType
303  $crit_id = $item->getId();
304  $crit_type = $item->getType();
305  $crit_title = $item->getTitle();
306  if ($crit_title == "") {
307  $crit_title = $item->getTranslatedType();
308  }
309 
310  if (!in_array($crit_title, $this->title_columns)) {
311  $this->title_columns[] = $crit_title;
312  }
313  switch ($crit_type) {
314  case 'bool':
315  if ($values[$crit_id] == 1) {
316  $this->excel->setCell($row, $col, $this->lng->txt("yes"));
317  } elseif ($values[$crit_id] == -1) {
318  $this->excel->setCell($row, $col, $this->lng->txt("no"));
319  }
320  break;
321  case 'rating':
322  /*
323  * Get the rating data from the DB in the current less expensive way.
324  * assignment_id -> used in il_rating.obj_id
325  * object type as string -> used in il_rating.obj_type
326  * participant id -> il_rating.sub_obj_id
327  * "peer_" + criteria_id -> il_rating.sub_obj_type (peer or e.g. peer_12)
328  * peer id -> il_rating.user_id
329  */
330  // Possible TODO: refactor ilExAssignment->getPeerReviewCriteriaCatalogueItems somehow to avoid client
331  // classes to deal with ilExCriteria instances with persistence (by id) or instances on the fly (by type)
332  $sub_obj_type = "peer";
333  if ($crit_id) {
334  $sub_obj_type .= "_" . $crit_id;
335  }
337  $this->assignment->getId(),
338  'ass',
340  $sub_obj_type,
341  $feedback_giver
342  );
343  if ($rating_int = round((int) $rating)) {
344  $this->excel->setCell($row, $col, $rating_int);
345  }
346  break;
347  case 'text':
348  //again another check for criteria id (if instantiated via type)
349  if ($crit_id) {
350  $this->excel->setCell($row, $col, $values[$crit_id]);
351  } else {
352  $this->excel->setCell($row, $col, $values['text']);
353  }
354  break;
355  case 'file':
356  if ($crit_id) {
358  $crit_file_obj = ilExcCriteriaFile::getInstanceById($crit_id);
359  } else {
360  $crit_file_obj = ilExcCriteriaFile::getInstanceByType($crit_type);
361  }
362  $crit_file_obj->setPeerReviewContext($this->assignment, $feedback_giver, $participant_id);
363  $files = $crit_file_obj->getFiles();
364 
365  $extra_crit_column = 0;
366  foreach ($files as $file) {
367  if ($extra_crit_column !== 0) {
368  $this->title_columns[] = $crit_title . "_" . $extra_crit_column;
369  }
370  $extra_crit_column++;
371  $dir = $this->getFeedbackDirectory($participant_id, $feedback_giver);
372  $this->copyFileToSubDirectory($dir, $file);
373  $this->excel->setCell($row, $col, "./" . $dir . DIRECTORY_SEPARATOR . basename($file));
374  $this->excel->addLink($row, $col, './' . $dir . DIRECTORY_SEPARATOR . basename($file));
375  $this->excel->setColors($this->excel->getCoordByColumnAndRow($col, $row), self::BG_COLOR, self::LINK_COLOR);
376  }
377  break;
378  }
379  }
380  }
381 
385  protected function getFeedbackDirectory(int $participant_id, int $feedback_giver): string
386  {
387  $dir = self::FBK_DIRECTORY . DIRECTORY_SEPARATOR .
388  "to_" . ilExSubmission::getDirectoryNameFromUserData($participant_id) . DIRECTORY_SEPARATOR .
389  "from_" . ilExSubmission::getDirectoryNameFromUserData($feedback_giver);
390  return $dir;
391  }
392 
397  public function getExtraColumnsForSubmissionFiles(int $a_obj_id, int $a_ass_id): int
398  {
399  global $DIC;
400  $ilDB = $DIC->database();
401 
402  $and = "";
403  if ($this->participant_id > 0) {
404  $and = " AND user_id = " . $this->participant_id;
405  }
406 
407  $query = "SELECT MAX(max_num) AS max" .
408  " FROM (SELECT COUNT(user_id) AS max_num FROM exc_returned" .
409  " WHERE obj_id=" . $a_obj_id . ". AND ass_id=" . $a_ass_id . $and . " AND mimetype IS NOT NULL" .
410  " GROUP BY user_id) AS COUNTS";
411 
412  $set = $ilDB->query($query);
413  $row = $ilDB->fetchAssoc($set);
414  return (int) $row['max'];
415  }
416 
417  // Mapping the links to use them on the excel.
418  public function addLink(
419  int $a_row,
420  int $a_col,
421  array $a_submission_file
422  ): void {
423  $user_id = $a_submission_file['user_id'];
424  $targetdir = ilExSubmission::getDirectoryNameFromUserData($user_id);
425 
426  $filepath = './' . $this->lng->txt("exc_ass_submission_zip") . DIRECTORY_SEPARATOR . $targetdir . DIRECTORY_SEPARATOR;
427  switch ($this->assignment->getType()) {
429  $filepath .= $a_submission_file['filetitle'];
430  break;
431 
433  $wsp_tree = new ilWorkspaceTree($user_id);
434  // #12939
435  if (!$wsp_tree->getRootId()) {
436  $wsp_tree->createTreeForUser($user_id);
437  }
438  $node = $wsp_tree->getNodeData((int) $a_submission_file['filetitle']);
439  $filepath .= "blog_" . $node['obj_id'] . DIRECTORY_SEPARATOR . "index.html";
440  break;
441 
443  $filepath .= "prt_" . $a_submission_file['filetitle'] . DIRECTORY_SEPARATOR . "index.html";
444  break;
445 
446  default:
447  $filepath = "";
448  }
449  $this->excel->addLink($a_row, $a_col, $filepath);
450  }
451 
461  protected function collectAssignmentData(int $assignment_id): void
462  {
463  $ass_has_feedback = false;
464  $ass_has_criteria = false;
465 
466  //assignment object
467  $this->assignment = new ilExAssignment($assignment_id);
468  $assignment_type = $this->assignment->getType();
469 
470  //Sanitized title for excel file and target directory.
471  $this->sanitized_title = ilFileUtils::getASCIIFilename($this->assignment->getTitle());
472 
473  // directories
474  if (!isset($this->temp_dir)) {
475  $this->createUniqueTempDirectory();
476  }
477  $this->createTargetDirectory();
478 
479  //Collect submission files if needed by assignment type.
480  if (in_array($assignment_type, $this->ass_types_with_files)) {
482  $this->collectSubmissionFiles();
483  }
484 
485  $first_excel_column_for_review = 0;
486  $col = 0;
487  $peer_review = null;
488  if ($this->assignment->getPeerReview()) {
489  $ass_has_feedback = true;
490  //obj to get the reviews in the foreach below.
491  $peer_review = new ilExPeerReview($this->assignment);
492  //default start column for revisions.
493  $first_excel_column_for_review = self::FIRST_DEFAULT_REVIEW_COLUMN;
494  }
495 
496  if ($this->isExcelNeeded($assignment_type, $ass_has_feedback)) {
497  // PhpSpreadsheet object
498  $this->excel = new ilExcel();
499 
500  //Excel sheet title
501  $this->excel->addSheet($this->sanitized_title);
502 
503  //add common excel Columns
504  #25585
505  $this->title_columns = array(
506  $this->lng->txt('lastname'),
507  $this->lng->txt('firstname'),
508  $this->lng->txt('login'),
509  $this->lng->txt('exc_last_submission')
510  );
511  switch ($assignment_type) {
513  $this->title_columns[] = $this->lng->txt("exc_submission_text");
514  break;
517  if ($assignment_type === ilExAssignment::TYPE_UPLOAD_TEAM) {
518  $first_excel_column_for_review++;
519  $this->title_columns[] = $this->lng->txt("exc_team");
520  }
521  $num_columns_submission = $this->getExtraColumnsForSubmissionFiles($this->exercise_id, $assignment_id);
522  if ($num_columns_submission > 1) {
523  for ($i = 1; $i <= $num_columns_submission; $i++) {
524  $this->title_columns[] = $this->lng->txt("exc_submission_file") . " " . $i;
525  }
526  } else {
527  $this->title_columns[] = $this->lng->txt("exc_submission_file");
528  }
529 
530  $first_excel_column_for_review += $num_columns_submission - 1;
531  break;
532  default:
533  $this->title_columns[] = $this->lng->txt("exc_submission");
534  break;
535  }
536  if ($ass_has_feedback) {
537  $this->title_columns[] = $this->lng->txt("exc_peer_review_giver");
538  $this->title_columns[] = $this->lng->txt('exc_last_submission');
539  }
540 
541  //criteria
542  //Notice:getPeerReviewCriteriaCatalogueItems can return just an empty instance without data.
543  if ($this->criteria_items = $this->assignment->getPeerReviewCriteriaCatalogueItems()) {
544  $ass_has_criteria = true;
545  }
546 
547  if ($this->participant_id > 0) {
548  $participants = array($this->participant_id);
549  } else {
550  $participants = $this->getAssignmentMembersIds();
551  }
552 
553  $filter = new ilExerciseMembersFilter($this->exercise_ref_id, $participants, $this->user_id);
554  $participants = $filter->filterParticipantsByAccess();
555 
556  $row = 2;
557  // Fill the excel
558  foreach ($participants as $participant_id) {
559  if (!is_null($this->selected_participants) && !in_array($participant_id, $this->selected_participants)) {
560  continue;
561  }
562  $submission = new ilExSubmission($this->assignment, $participant_id);
563  $submission_files = $submission->getFiles();
564 
565  if ($submission_files !== []) {
566  $participant_name = ilObjUser::_lookupName($participant_id);
567  $this->excel->setCell($row, self::PARTICIPANT_LASTNAME_COLUMN, $participant_name['lastname']);
568  $this->excel->setCell($row, self::PARTICIPANT_FIRSTNAME_COLUMN, $participant_name['firstname']);
569  $this->excel->setCell($row, self::PARTICIPANT_LOGIN_COLUMN, $participant_name['login']);
570 
571  //Get the submission Text
572  if (!in_array($assignment_type, $this->ass_types_with_files)) {
573  foreach ($submission_files as $submission_file) {
574  $this->excel->setCell($row, self::SUBMISSION_DATE_COLUMN, $submission_file['timestamp']);
575  $this->excel->setCell($row, self::FIRST_DEFAULT_SUBMIT_COLUMN, $submission_file['atext']);
576  }
577  } else {
578  $col = self::FIRST_DEFAULT_SUBMIT_COLUMN;
579  if ($assignment_type === ilExAssignment::TYPE_UPLOAD_TEAM) {
580  $team_id = $this->team->getTeamForMember($this->assignment->getId(), $participant_id);
581  $this->excel->setCell($row, $col, (string) $team_id);
582  $col++;
583  }
584  foreach ($submission_files as $submission_file) {
585  $this->excel->setCell($row, self::SUBMISSION_DATE_COLUMN, $submission_file['timestamp']);
586 
587  if ($assignment_type == ilExAssignment::TYPE_PORTFOLIO || $assignment_type == ilExAssignment::TYPE_BLOG) {
588  $this->excel->setCell($row, $col, $this->lng->txt("open"));
589  } else {
590  $this->excel->setCell($row, $col, $submission_file['filetitle']);
591  }
592  $this->excel->setColors($this->excel->getCoordByColumnAndRow($col, $row), self::BG_COLOR, self::LINK_COLOR);
593  if ($assignment_type != ilExAssignment::TYPE_UPLOAD_TEAM) {
594  $this->addLink($row, $col, $submission_file);
595  }
596  $col++; //does not affect blogs and portfolios.
597  }
598  }
599 
600  if ($ass_has_feedback) {
601  if ($col < $first_excel_column_for_review) {
602  $col = $first_excel_column_for_review;
603  }
604  $reviews = [];
605  if ($peer_review !== null) {
606  $reviews = $peer_review->getPeerReviewsByPeerId($participant_id);
607  }
608 
609  //extra lines
610  $current_review_row = 0;
611  foreach ($reviews as $review) {
612  //not all reviews are done, we check it via date of review.
613  if ($review['tstamp']) {
614  $current_review_row++;
615  if ($current_review_row > 1) {
616  for ($i = 0; $i < $first_excel_column_for_review; $i++) {
617  $cell_to_copy = $this->excel->getCell($row, $i);
618  $this->excel->setCell($row + 1, $i, $cell_to_copy);
619  if ($i >= self::FIRST_DEFAULT_SUBMIT_COLUMN) {
620  $this->excel->setColors($this->excel->getCoordByColumnAndRow($i, $row + 1), self::BG_COLOR, self::LINK_COLOR);
621  }
622  }
623  ++$row;
624  }
625 
626  $feedback_giver = $review['giver_id']; // user who made the review.
627 
628  $feedback_giver_name = ilObjUser::_lookupName($feedback_giver);
629 
630  $this->excel->setCell(
631  $row,
632  $col,
633  $feedback_giver_name['lastname'] . ", " . $feedback_giver_name['firstname'] . " [" . $feedback_giver_name['login'] . "]"
634  );
635 
636  $this->excel->setCell($row, $col + 1, $review['tstamp']);
637 
638  if ($ass_has_criteria) {
639  $this->addCriteriaToExcel($feedback_giver, $participant_id, $row, $col + 1);
640  }
641  }
642  }
643  }
644 
645  $row++;
646  }
647  }
648 
649  $this->addColumnTitles();
650  $this->excel->writeToFile($this->target_directory . "/" . $this->sanitized_title);
651  }
652  }
653 
658  public function getAssignmentMembersIds(): array
659  {
660  global $DIC;
661 
662  $ilDB = $DIC->database();
663  $members = array();
664 
665  $set = $ilDB->query("SELECT usr_id" .
666  " FROM exc_mem_ass_status" .
667  " WHERE ass_id = " . $ilDB->quote($this->assignment->getId(), "integer"));
668 
669  while ($rec = $ilDB->fetchAssoc($set)) {
670  if (!\ilObjUser::userExists([(int) $rec['usr_id']])) {
671  continue;
672  }
673  $members[] = $rec['usr_id'];
674  }
675 
676  return $members;
677  }
678 }
getAssignmentMembersIds()
get ONLY the members ids for this assignment
Exercise assignment.
static downloadAllAssignmentFiles(ilExAssignment $a_ass, array $members, string $to_path)
Download all submitted files of an assignment (all user)
static _lookupName(int $a_user_id)
lookup user name
collectAssignmentData(int $assignment_id)
write assignment data to excel file
static getInstanceByType(string $a_type)
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...
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
createSubmissionsDirectory()
Create the directory with the assignment title.
const TYPE_UPLOAD
direct checks against const should be avoided, use type objects instead
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$path
Definition: ltiservices.php:32
static getASCIIFilename(string $a_filename)
createTargetDirectory()
Create the directory with the assignment title.
global $DIC
Definition: feed.php:28
Exercise peer review.
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
Class ilObjExercise.
static userExists(array $a_usr_ids=[])
addLink(int $a_row, int $a_col, array $a_submission_file)
$out
Definition: buildRTE.php:24
static createDirectory(string $a_dir, int $a_mod=0755)
create directory
run(array $input, Observer $observer)
run the job
copyFileToSubDirectory(string $a_directory, string $a_file)
Copy a file in the Feedback_files directory TODO use the new filesystem.
getFeedbackDirectory(int $participant_id, int $feedback_giver)
see also bug https://mantis.ilias.de/view.php?id=30999
static getDirectoryNameFromUserData(int $a_user_id)
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 ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
static getRatingForUserAndObject(int $a_obj_id, string $a_obj_type, int $a_sub_obj_id, string $a_sub_obj_type, int $a_user_id, int $a_category_id=null)
Get rating for a user and an object.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getExtraColumnsForSubmissionFiles(int $a_obj_id, int $a_ass_id)
Get the number of max amount of files submitted by a single user in the assignment.
static getInstanceById(int $a_id)