1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/** @defgroup ModulesExercise Modules/Exercise
5 */
6
7/**
8* Class ilObjExercise
9*
10* @author Stefan Meyer <meyer@leifos.com>
11* @author Michael Jansen <mjansen@databay.de>
12*
13* @ingroup ModulesExercise
14*/
15class ilObjExercise extends ilObject
16{
17    /**
18     * @var ilObjUser
19     */
20    protected $user;
21
22    public $file_obj;
23    public $members_obj;
24    public $files;
25
26    public $timestamp;
27    public $hour;
28    public $minutes;
29    public $day;
30    public $month;
31    public $year;
32    public $instruction;
33    public $certificate_visibility;
34
35    public $tutor_feedback = 7; // [int]
36
37    /**
38     * @var int number of mandatory assignments in random pass mode
39     */
40    protected $nr_random_mand;
41
42    const TUTOR_FEEDBACK_MAIL = 1;
43    const TUTOR_FEEDBACK_TEXT = 2;
44    const TUTOR_FEEDBACK_FILE = 4;
45
46    const PASS_MODE_NR = "nr";
47    const PASS_MODE_ALL = "all";
48    const PASS_MODE_RANDOM = "random";
49
50    /**
51     *
52     * Indicates whether completion by submission is enabled or not
53     *
54     * @var boolean
55     * @access protected
56     *
57     */
58    protected $completion_by_submission = false;
59
60    /**
61     * @var \ILIAS\Filesystem\Filesystem
62     */
63    private $webFilesystem;
64
65    /**
66     * @var ilExcMandatoryAssignmentManager
67     */
68    protected $mandatory_manager;
69
70    /**
71    * Constructor
72    * @access	public
73    * @param	integer	reference_id or object_id
74    * @param	boolean	treat the id as reference_id (true) or object_id (false)
75    */
76    public function __construct($a_id = 0, $a_call_by_reference = true)
77    {
78        global $DIC;
79
80        $this->db = $DIC->database();
81        $this->app_event_handler = $DIC["ilAppEventHandler"];
82        $this->lng = $DIC->language();
83        $this->user = $DIC->user();
84        $this->setPassMode("all");
85        $this->type = "exc";
86        $this->webFilesystem = $DIC->filesystem()->web();
87
88        parent::__construct($a_id, $a_call_by_reference);
89        $this->mandatory_manager = $DIC->exercise()->internal()->service()->getMandatoryAssignmentManager($this);
90    }
91
92    /**
93     * Set id
94     * @param int $id
95     */
96    public function setId($id)
97    {
98        global $DIC;
99        parent::setId($id);
100        // this is needed, since e.g. ilObjectFactory initialises the object with id 0 and later sets the id
101        $this->mandatory_manager = $DIC->exercise()->internal()->service()->getMandatoryAssignmentManager($this);
102    }
103
104    // SET, GET METHODS
105    public function setDate($a_hour, $a_minutes, $a_day, $a_month, $a_year)
106    {
107        $this->hour = (int) $a_hour;
108        $this->minutes = (int) $a_minutes;
109        $this->day = (int) $a_day;
110        $this->month = (int) $a_month;
111        $this->year = (int) $a_year;
112        $this->timestamp = mktime($this->hour, $this->minutes, 0, $this->month, $this->day, $this->year);
113        return true;
114    }
115    public function getTimestamp()
116    {
117        return $this->timestamp;
118    }
119    public function setTimestamp($a_timestamp)
120    {
121        $this->timestamp = $a_timestamp;
122    }
123    public function setInstruction($a_instruction)
124    {
125        $this->instruction = $a_instruction;
126    }
127    public function getInstruction()
128    {
129        return $this->instruction;
130    }
131
132    /**
133     * Set pass mode (all | nr)
134     *
135     * @param	string		pass mode
136     */
137    public function setPassMode($a_val)
138    {
139        $this->pass_mode = $a_val;
140    }
141
142    /**
143     * Get pass mode (all | nr)
144     *
145     * @return	string		pass mode
146     */
147    public function getPassMode()
148    {
149        return $this->pass_mode;
150    }
151
152    /**
153     * Set number of assignments that must be passed to pass the exercise
154     *
155     * @param	integer		pass nr
156     */
157    public function setPassNr($a_val)
158    {
159        $this->pass_nr = $a_val;
160    }
161
162    /**
163     * Get number of assignments that must be passed to pass the exercise
164     *
165     * @return	integer		pass nr
166     */
167    public function getPassNr()
168    {
169        return $this->pass_nr;
170    }
171
172    /**
173     * Set whether submissions of learners should be shown to other learners after deadline
174     *
175     * @param	boolean		show submissions
176     */
177    public function setShowSubmissions($a_val)
178    {
179        $this->show_submissions = $a_val;
180    }
181
182    /**
183     * Get whether submissions of learners should be shown to other learners after deadline
184     *
185     * @return	integer		show submissions
186     */
187    public function getShowSubmissions()
188    {
189        return $this->show_submissions;
190    }
191
192    /**
193     * Set number of mandatory assignments in random pass mode
194     *
195     * @param int $a_val
196     */
197    public function setNrMandatoryRandom($a_val)
198    {
199        $this->nr_random_mand = $a_val;
200    }
201
202    /**
203     * Get number of mandatory assignments in random pass mode
204     *
205     * @return int
206     */
207    public function getNrMandatoryRandom()
208    {
209        return $this->nr_random_mand;
210    }
211
212
213    /*	function getFiles()
214        {
215            return $this->files;
216        }*/
217
218    public function checkDate()
219    {
220        return	$this->hour == (int) date("H", $this->timestamp) and
221            $this->minutes == (int) date("i", $this->timestamp) and
222            $this->day == (int) date("d", $this->timestamp) and
223            $this->month == (int) date("m", $this->timestamp) and
224            $this->year == (int) date("Y", $this->timestamp);
225    }
226
227    public function hasTutorFeedbackText()
228    {
229        return $this->tutor_feedback & self::TUTOR_FEEDBACK_TEXT;
230    }
231
232    public function hasTutorFeedbackMail()
233    {
234        return $this->tutor_feedback & self::TUTOR_FEEDBACK_MAIL;
235    }
236
237    public function hasTutorFeedbackFile()
238    {
239        return $this->tutor_feedback & self::TUTOR_FEEDBACK_FILE;
240    }
241
242    protected function getTutorFeedback()
243    {
244        return $this->tutor_feedback;
245    }
246
247    public function setTutorFeedback($a_value)
248    {
249        $this->tutor_feedback = $a_value;
250    }
251
252    public function saveData()
253    {
254        $ilDB = $this->db;
255
256        $ilDB->insert("exc_data", array(
257            "obj_id" => array("integer", $this->getId()),
258            "instruction" => array("clob", $this->getInstruction()),
259            "time_stamp" => array("integer", $this->getTimestamp()),
260            "pass_mode" => array("text", $this->getPassMode()),
261            "nr_mandatory_random" => array("integer", (int) $this->getNrMandatoryRandom()),
262            "pass_nr" => array("text", $this->getPassNr()),
263            "show_submissions" => array("integer", (int) $this->getShowSubmissions()),
264            'compl_by_submission' => array('integer', (int) $this->isCompletionBySubmissionEnabled()),
265            "certificate_visibility" => array("integer", (int) $this->getCertificateVisibility()),
266            "tfeedback" => array("integer", (int) $this->getTutorFeedback())
267            ));
268        return true;
269    }
270
271    /**
272     * Clone exercise (no member data)
273     *
274     * @access public
275     * @param int target ref_id
276     * @param int copy id
277     */
278    public function cloneObject($a_target_id, $a_copy_id = 0, $a_omit_tree = false)
279    {
280        $ilDB = $this->db;
281
282        // Copy settings
283        $new_obj = parent::cloneObject($a_target_id, $a_copy_id, $a_omit_tree);
284        $new_obj->setInstruction($this->getInstruction());
285        $new_obj->setTimestamp($this->getTimestamp());
286        $new_obj->setPassMode($this->getPassMode());
287        $new_obj->setNrMandatoryRandom($this->getNrMandatoryRandom());
288        $new_obj->saveData();
289        $new_obj->setPassNr($this->getPassNr());
290        $new_obj->setShowSubmissions($this->getShowSubmissions());
291        $new_obj->setCompletionBySubmission($this->isCompletionBySubmissionEnabled());
292        $new_obj->setTutorFeedback($this->getTutorFeedback());
293        $new_obj->setCertificateVisibility($this->getCertificateVisibility());
294        $new_obj->update();
295
296        $new_obj->saveCertificateVisibility($this->getCertificateVisibility());
297
298        // Copy criteria catalogues
299        $crit_cat_map = array();
300        foreach (ilExcCriteriaCatalogue::getInstancesByParentId($this->getId()) as $crit_cat) {
301            $new_id = $crit_cat->cloneObject($new_obj->getId());
302            $crit_cat_map[$crit_cat->getId()] = $new_id;
303        }
304
305        // Copy assignments
306        ilExAssignment::cloneAssignmentsOfExercise($this->getId(), $new_obj->getId(), $crit_cat_map);
307
308        // Copy learning progress settings
309        $obj_settings = new ilLPObjSettings($this->getId());
310        $obj_settings->cloneSettings($new_obj->getId());
311        unset($obj_settings);
312
313        $pathFactory = new ilCertificatePathFactory();
314        $templateRepository = new ilCertificateTemplateRepository($ilDB);
315
316        $cloneAction = new ilCertificateCloneAction(
317            $ilDB,
318            $pathFactory,
319            $templateRepository,
320            $this->webFilesystem,
321            $this->log,
322            new ilCertificateObjectHelper()
323        );
324
325        $cloneAction->cloneCertificate($this, $new_obj);
326
327        // additional features
328        foreach (ilContainer::_getContainerSettings($this->getId()) as $keyword => $value) {
329            ilContainer::_writeContainerSetting($new_obj->getId(), $keyword, $value);
330        }
331
332        // org unit setting
333        $orgu_object_settings = new ilOrgUnitObjectPositionSetting($new_obj->getId());
334        $orgu_object_settings->setActive(
335            (int) ilOrgUnitGlobalSettings::getInstance()->isPositionAccessActiveForObject($this->getId())
336        );
337        $orgu_object_settings->update();
338
339        return $new_obj;
340    }
341
342    /**
343    * delete course and all related data
344    *
345    * @access	public
346    * @return	boolean	true if all object data were removed; false if only a references were removed
347    */
348    public function delete()
349    {
350        $ilDB = $this->db;
351        $ilAppEventHandler = $this->app_event_handler;
352
353        // always call parent delete function first!!
354        if (!parent::delete()) {
355            return false;
356        }
357        // put here course specific stuff
358        $ilDB->manipulate("DELETE FROM exc_data " .
359            "WHERE obj_id = " . $ilDB->quote($this->getId(), "integer"));
360
361        ilExcCriteriaCatalogue::deleteByParent($this->getId());
362
363        // remove all notifications
364        ilNotification::removeForObject(ilNotification::TYPE_EXERCISE_SUBMISSION, $this->getId());
365
366        $ilAppEventHandler->raise(
367            'Modules/Exercise',
368            'delete',
369            array('obj_id' => $this->getId())
370        );
371
372        return true;
373    }
374
375    public function read()
376    {
377        $ilDB = $this->db;
378
379        parent::read();
380
381        $query = "SELECT * FROM exc_data " .
382            "WHERE obj_id = " . $ilDB->quote($this->getId(), "integer");
383
384        $res = $ilDB->query($query);
385        while ($row = $ilDB->fetchObject($res)) {
386            $this->setInstruction($row->instruction);
387            $this->setTimestamp($row->time_stamp);
388            $pm = ($row->pass_mode == "")
389                ? "all"
390                : $row->pass_mode;
391            $this->setPassMode($pm);
392            $this->setShowSubmissions($row->show_submissions);
393            if ($row->pass_mode == "nr") {
394                $this->setPassNr($row->pass_nr);
395            }
396            $this->setNrMandatoryRandom($row->nr_mandatory_random);
397            $this->setCompletionBySubmission($row->compl_by_submission == 1 ? true : false);
398            $this->setCertificateVisibility($row->certificate_visibility);
399            $this->setTutorFeedback($row->tfeedback);
400        }
401
402        $this->members_obj = new ilExerciseMembers($this);
403
404        return true;
405    }
406
407    public function update()
408    {
409        $ilDB = $this->db;
410
411        parent::update();
412
413        if ($this->getPassMode() == "all") {
414            $pass_nr = null;
415        } else {
416            $pass_nr = $this->getPassNr();
417        }
418
419        $ilDB->update("exc_data", array(
420            "instruction" => array("clob", $this->getInstruction()),
421            "time_stamp" => array("integer", $this->getTimestamp()),
422            "pass_mode" => array("text", $this->getPassMode()),
423            "pass_nr" => array("integer", $this->getPassNr()),
424            "nr_mandatory_random" => array("integer", (int) $this->getNrMandatoryRandom()),
425            "show_submissions" => array("integer", (int) $this->getShowSubmissions()),
426            'compl_by_submission' => array('integer', (int) $this->isCompletionBySubmissionEnabled()),
427            'tfeedback' => array('integer', (int) $this->getTutorFeedback()),
428            ), array(
429            "obj_id" => array("integer", $this->getId())
430            ));
431
432        $this->updateAllUsersStatus();
433
434        return true;
435    }
436
437    /**
438     * send exercise per mail to members
439     */
440    public function sendAssignment(ilExAssignment $a_ass, $a_members)
441    {
442        $lng = $this->lng;
443        $ilUser = $this->user;
444
445        $lng->loadLanguageModule("exc");
446
447        // subject
448        $subject = $a_ass->getTitle()
449            ? $this->getTitle() . ": " . $a_ass->getTitle()
450            : $this->getTitle();
451
452
453        // body
454
455        $body = $a_ass->getInstruction();
456        $body .= "\n\n";
457
458        $body .= $lng->txt("exc_edit_until") . ": ";
459        $body .= (!$a_ass->getDeadline())
460          ? $lng->txt("exc_no_deadline_specified")
461          : ilDatePresentation::formatDate(new ilDateTime($a_ass->getDeadline(), IL_CAL_UNIX));
462        $body .= "\n\n";
463
464        $body .= ilLink::_getLink($this->getRefId(), "exc");
465
466
467        // files
468        $file_names = array();
469        $storage = new ilFSStorageExercise($a_ass->getExerciseId(), $a_ass->getId());
470        $files = $storage->getFiles();
471        if (count($files)) {
472            $mfile_obj = new ilFileDataMail($GLOBALS['DIC']['ilUser']->getId());
473            foreach ($files as $file) {
474                $mfile_obj->copyAttachmentFile($file["fullpath"], $file["name"]);
475                $file_names[] = $file["name"];
476            }
477        }
478
479        // recipients
480        $recipients = array();
481        foreach ($a_members as $member_id) {
482            $tmp_obj = ilObjectFactory::getInstanceByObjId($member_id);
483            $recipients[] = $tmp_obj->getLogin();
484            unset($tmp_obj);
485        }
486        $recipients = implode(",", $recipients);
487
488        // send mail
489        $tmp_mail_obj = new ilMail($ilUser->getId());
490        $errors = $tmp_mail_obj->enqueue(
491            $recipients,
492            "",
493            "",
494            $subject,
495            $body,
496            $file_names
497        );
498        unset($tmp_mail_obj);
499
500        // remove tmp files
501        if (sizeof($file_names)) {
502            $mfile_obj->unlinkFiles($file_names);
503            unset($mfile_obj);
504        }
505
506        // set recipients mail status
507        foreach ($a_members as $member_id) {
508            $member_status = $a_ass->getMemberStatus($member_id);
509            $member_status->setSent(true);
510            $member_status->update();
511        }
512
513        return true;
514    }
515
516    /**
517     * Determine status of user
518     */
519    public function determinStatusOfUser($a_user_id = 0)
520    {
521        $ilUser = $this->user;
522
523        $mandatory_manager = $this->mandatory_manager;
524
525        if ($a_user_id == 0) {
526            $a_user_id = $ilUser->getId();
527        }
528
529        $ass = ilExAssignment::getInstancesByExercise($this->getId());
530
531        $passed_all_mandatory = true;
532        $failed_a_mandatory = false;
533        $cnt_passed = 0;
534        $cnt_notgraded = 0;
535
536        /** @var ilExAssignment $a */
537        foreach ($ass as $a) {
538            $stat = $a->getMemberStatus($a_user_id)->getStatus();
539            $mandatory = $mandatory_manager->isMandatoryForUser($a->getId(), $a_user_id);
540            if ($mandatory && ($stat == "failed" || $stat == "notgraded")) {
541                $passed_all_mandatory = false;
542            }
543            if ($mandatory && ($stat == "failed")) {
544                $failed_a_mandatory = true;
545            }
546            if ($stat == "passed") {
547                $cnt_passed++;
548            }
549            if ($stat == "notgraded") {
550                $cnt_notgraded++;
551            }
552        }
553
554        if (count($ass) == 0) {
555            $passed_all_mandatory = false;
556        }
557
558        if ($this->getPassMode() == self::PASS_MODE_ALL) {
559            $overall_stat = "notgraded";
560            if ($failed_a_mandatory) {
561                $overall_stat = "failed";
562            } elseif ($passed_all_mandatory && $cnt_passed > 0) {
563                $overall_stat = "passed";
564            }
565        } elseif ($this->getPassMode() == self::PASS_MODE_NR) {
566            $min_nr = $this->getPassNr();
567            $overall_stat = "notgraded";
568            if ($failed_a_mandatory || ($cnt_passed + $cnt_notgraded < $min_nr)) {
569                $overall_stat = "failed";
570            } elseif ($passed_all_mandatory && $cnt_passed >= $min_nr) {
571                $overall_stat = "passed";
572            }
573        } elseif ($this->getPassMode() == self::PASS_MODE_RANDOM) {
574            $overall_stat = "notgraded";
575            if ($failed_a_mandatory) {
576                $overall_stat = "failed";
577            } elseif ($passed_all_mandatory && $cnt_passed > 0) {
578                $overall_stat = "passed";
579            }
580        }
581
582        $ret = array(
583            "overall_status" => $overall_stat,
584            "failed_a_mandatory" => $failed_a_mandatory);
585        //echo "<br>p:".$cnt_passed.":ng:".$cnt_notgraded;
586        //var_dump($ret); exit;
587        return $ret;
588    }
589
590    /**
591     * Update exercise status of user
592     */
593    public function updateUserStatus($a_user_id = 0)
594    {
595        $ilUser = $this->user;
596
597        if ($a_user_id == 0) {
598            $a_user_id = $ilUser->getId();
599        }
600
601        $st = $this->determinStatusOfUser($a_user_id);
602
603        ilExerciseMembers::_writeStatus(
604            $this->getId(),
605            $a_user_id,
606            $st["overall_status"]
607        );
608    }
609
610    /**
611     * Update status of all users
612     */
613    public function updateAllUsersStatus()
614    {
615        if (!is_object($this->members_obj)) {
616            $this->members_obj = new ilExerciseMembers($this);
617        }
618
619        $mems = $this->members_obj->getMembers();
620        foreach ($mems as $mem) {
621            $this->updateUserStatus($mem);
622        }
623    }
624
625    /**
626     * Exports grades as excel
627     */
628    public function exportGradesExcel()
629    {
630        $ass_data = ilExAssignment::getInstancesByExercise($this->getId());
631
632        $excel = new ilExcel();
633        $excel->addSheet($this->lng->txt("exc_status"));
634
635
636        //
637        // status
638        //
639
640        // header row
641        $row = $cnt = 1;
642        $excel->setCell($row, 0, $this->lng->txt("name"));
643        foreach ($ass_data as $ass) {
644            $excel->setCell($row, $cnt++, ($cnt / 2) . " - ". $this->lng->txt("exc_tbl_status"));
645            $excel->setCell($row, $cnt++, (($cnt - 1) / 2) . " - ". $this->lng->txt("exc_tbl_mark"));
646        }
647        $excel->setCell($row, $cnt++, $this->lng->txt("exc_total_exc"));
648        $excel->setCell($row, $cnt++, $this->lng->txt("exc_mark"));
649        $excel->setCell($row++, $cnt, $this->lng->txt("exc_comment_for_learner"));
650        $excel->setBold("A1:" . $excel->getColumnCoord($cnt) . "1");
651
652        // data rows
653        $mem_obj = new ilExerciseMembers($this);
654
655        $filtered_members = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
656            'edit_submissions_grades',
657            'edit_submissions_grades',
658            $this->getRefId(),
659            (array) $mem_obj->getMembers()
660        );
661
662        foreach ((array) $filtered_members as $user_id) {
663            $mems[$user_id] = ilObjUser::_lookupName($user_id);
664        }
665        $mems = ilUtil::sortArray($mems, "lastname", "asc", false, true);
666
667        foreach ($mems as $user_id => $d) {
668            $col = 0;
669
670            // name
671            $excel->setCell($row, $col++, $d["lastname"] . ", " . $d["firstname"] . " [" . $d["login"] . "]");
672
673            reset($ass_data);
674            foreach ($ass_data as $ass) {
675                $status = $ass->getMemberStatus($user_id)->getStatus();
676                $mark = $ass->getMemberStatus($user_id)->getMark();
677                $excel->setCell($row, $col++, $this->lng->txt("exc_" . $status));
678                $excel->setCell($row, $col++, $mark);
679            }
680
681            // total status
682            $status = ilExerciseMembers::_lookupStatus($this->getId(), $user_id);
683            $excel->setCell($row, $col++, $this->lng->txt("exc_" . $status));
684
685            // #18096
686            $marks_obj = new ilLPMarks($this->getId(), $user_id);
687            $excel->setCell($row, $col++, $marks_obj->getMark());
688            $excel->setCell($row++, $col, $marks_obj->getComment());
689        }
690
691
692        //
693        // mark
694        //
695
696        $excel->addSheet($this->lng->txt("exc_mark"));
697
698        // header row
699        $row = $cnt = 1;
700        $excel->setCell($row, 0, $this->lng->txt("name"));
701        foreach ($ass_data as $ass) {
702            $excel->setCell($row, $cnt++, $cnt - 1);
703        }
704        $excel->setCell($row++, $cnt++, $this->lng->txt("exc_total_exc"));
705        $excel->setBold("A1:" . $excel->getColumnCoord($cnt) . "1");
706
707        // data rows
708        reset($mems);
709        foreach ($mems as $user_id => $d) {
710            $col = 0;
711
712            // name
713            $d = ilObjUser::_lookupName($user_id);
714            $excel->setCell($row, $col++, $d["lastname"] . ", " . $d["firstname"] . " [" . $d["login"] . "]");
715
716            reset($ass_data);
717            foreach ($ass_data as $ass) {
718                $excel->setCell($row, $col++, $ass->getMemberStatus($user_id)->getMark());
719            }
720
721            // total mark
722            $excel->setCell($row++, $col, ilLPMarks::_lookupMark($user_id, $this->getId()));
723        }
724
725        $exc_name = ilUtil::getASCIIFilename(preg_replace("/\s/", "_", $this->getTitle()));
726        $excel->sendToClient($exc_name);
727    }
728
729    /**
730     * Send feedback file notification to user
731     */
732    public function sendFeedbackFileNotification($a_feedback_file, $a_user_id, $a_ass_id, $a_is_text_feedback = false)
733    {
734        $user_ids = $a_user_id;
735        if (!is_array($user_ids)) {
736            $user_ids = array($user_ids);
737        }
738
739        $type = (bool) $a_is_text_feedback
740            ? ilExerciseMailNotification::TYPE_FEEDBACK_TEXT_ADDED
741            : ilExerciseMailNotification::TYPE_FEEDBACK_FILE_ADDED;
742
743        $not = new ilExerciseMailNotification();
744        $not->setType($type);
745        $not->setAssignmentId($a_ass_id);
746        $not->setObjId($this->getId());
747        if ($this->getRefId() > 0) {
748            $not->setRefId($this->getRefId());
749        }
750        $not->setRecipients($user_ids);
751        $not->send();
752    }
753
754    /**
755     *
756     * Checks whether completion by submission is enabled or not
757     *
758     * @return	boolean
759     * @access	public
760     *
761     */
762    public function isCompletionBySubmissionEnabled()
763    {
764        return $this->completion_by_submission;
765    }
766
767    /**
768     *
769     * Enabled/Disable completion by submission
770     *
771     * @param	boolean
772     * @return	ilObjExercise
773     * @access	public
774     *
775     */
776    public function setCompletionBySubmission($bool)
777    {
778        $this->completion_by_submission = (bool) $bool;
779
780        return $this;
781    }
782
783    public function processExerciseStatus(ilExAssignment $a_ass, array $a_user_ids, $a_has_submitted, array $a_valid_submissions = null)
784    {
785        $a_has_submitted = (bool) $a_has_submitted;
786
787        foreach ($a_user_ids as $user_id) {
788            $member_status = $a_ass->getMemberStatus($user_id);
789            $member_status->setReturned($a_has_submitted);
790            $member_status->update();
791
792            ilExerciseMembers::_writeReturned($this->getId(), $user_id, $a_has_submitted);
793        }
794
795        // re-evaluate exercise status
796        if ($this->isCompletionBySubmissionEnabled()) {
797            foreach ($a_user_ids as $user_id) {
798                $status = 'notgraded';
799                if ($a_has_submitted) {
800                    if (!is_array($a_valid_submissions) ||
801                        $a_valid_submissions[$user_id]) {
802                        $status = 'passed';
803                    }
804                }
805
806                $member_status = $a_ass->getMemberStatus($user_id);
807                $member_status->setStatus($status);
808                $member_status->update();
809            }
810        }
811    }
812
813    /**
814     * Get all exercises for user
815     *
816     * @param <type> $a_user_id
817     * @return array (exercise id => passed)
818     */
819    public static function _lookupFinishedUserExercises($a_user_id)
820    {
821        global $DIC;
822
823        $ilDB = $DIC->database();
824
825        $set = $ilDB->query("SELECT obj_id, status FROM exc_members" .
826            " WHERE usr_id = " . $ilDB->quote($a_user_id, "integer") .
827            " AND (status = " . $ilDB->quote("passed", "text") .
828            " OR status = " . $ilDB->quote("failed", "text") . ")");
829
830        $all = array();
831        while ($row = $ilDB->fetchAssoc($set)) {
832            $all[$row["obj_id"]] = ($row["status"] == "passed");
833        }
834        return $all;
835    }
836
837
838    /**
839    * Returns the visibility settings of the certificate
840    *
841    * @return integer The value for the visibility settings (0 = always, 1 = only passed,  2 = never)
842    * @access public
843    */
844    public function getCertificateVisibility()
845    {
846        return (strlen($this->certificate_visibility)) ? $this->certificate_visibility : 0;
847    }
848
849    /**
850    * Sets the visibility settings of the certificate
851    *
852    * @param integer $a_value The value for the visibility settings (0 = always, 1 = only passed,  2 = never)
853    * @access public
854    */
855    public function setCertificateVisibility($a_value)
856    {
857        $this->certificate_visibility = $a_value;
858    }
859
860    /**
861    * Saves the visibility settings of the certificate
862    *
863    * @param integer $a_value The value for the visibility settings (0 = always, 1 = only passed,  2 = never)
864    * @access private
865    */
866    public function saveCertificateVisibility($a_value)
867    {
868        $ilDB = $this->db;
869
870        $affectedRows = $ilDB->manipulateF(
871            "UPDATE exc_data SET certificate_visibility = %s WHERE obj_id = %s",
872            array('integer', 'integer'),
873            array($a_value, $this->getId())
874        );
875    }
876
877    /**
878     * Add to desktop after hand-in
879     *
880     * @return bool
881     */
882    public function hasAddToDesktop()
883    {
884        $exc_set = new ilSetting("excs");
885        return (bool) $exc_set->get("add_to_pd", true);
886    }
887}
888