MDL-20904, upload function now upload to user private only, remove file_stroage code...
[moodle.git] / mod / assignment / type / upload / assignment.class.php
1 <?php
2 require_once($CFG->libdir . '/portfoliolib.php');
3 require_once($CFG->dirroot . '/mod/assignment/lib.php');
5 define('ASSIGNMENT_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
6 define('ASSIGNMENT_STATUS_CLOSED', 'closed');       // teacher prevents more submissions
8 /**
9  * Extend the base assignment class for assignments where you upload a single file
10  *
11  */
12 class assignment_upload extends assignment_base {
14     function assignment_upload($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
15         parent::assignment_base($cmid, $assignment, $cm, $course);
16         $this->type = 'upload';
17     }
19     function view() {
20         global $USER, $OUTPUT;
22         require_capability('mod/assignment:view', $this->context);
24         add_to_log($this->course->id, 'assignment', 'view', "view.php?id={$this->cm->id}", $this->assignment->id, $this->cm->id);
26         $this->view_header();
28         if ($this->assignment->timeavailable > time()
29           and !has_capability('mod/assignment:grade', $this->context)      // grading user can see it anytime
30           and $this->assignment->var3) {                                   // force hiding before available date
31             echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
32             print_string('notavailableyet', 'assignment');
33             echo $OUTPUT->box_end();
34         } else {
35             $this->view_intro();
36         }
38         $this->view_dates();
40         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')) {
41             if ($submission = $this->get_submission($USER->id)) {
42                 $filecount = $this->count_user_files($submission->id);
43             } else {
44                 $filecount = 0;
45             }
47             $this->view_feedback();
49             if (!$this->drafts_tracked() or !$this->isopen() or $this->is_finalized($submission)) {
50                 echo $OUTPUT->heading(get_string('submission', 'assignment'), 3);
51             } else {
52                 echo $OUTPUT->heading(get_string('submissiondraft', 'assignment'), 3);
53             }
55             if ($filecount and $submission) {
56                 echo $OUTPUT->box($this->print_user_files($USER->id, true), 'generalbox boxaligncenter', 'userfiles');
57             } else {
58                 if (!$this->isopen() or $this->is_finalized($submission)) {
59                     echo $OUTPUT->box(get_string('nofiles', 'assignment'), 'generalbox boxaligncenter nofiles', 'userfiles');
60                 } else {
61                     echo $OUTPUT->box(get_string('nofilesyet', 'assignment'), 'generalbox boxaligncenter nofiles', 'userfiles');
62                 }
63             }
65             $this->view_upload_form();
67             if ($this->notes_allowed()) {
68                 echo $OUTPUT->heading(get_string('notes', 'assignment'), 3);
69                 $this->view_notes();
70             }
72             $this->view_final_submission();
73         }
74         $this->view_footer();
75     }
78     function view_feedback($submission=NULL) {
79         global $USER, $CFG, $DB, $OUTPUT;
80         require_once($CFG->libdir.'/gradelib.php');
82         if (!$submission) { /// Get submission for this assignment
83             $submission = $this->get_submission($USER->id);
84         }
86         if (empty($submission->timemarked)) {   /// Nothing to show, so print nothing
87             if ($this->count_responsefiles($USER->id)) {
88                 echo $OUTPUT->heading(get_string('responsefiles', 'assignment'), 3);
89                 $responsefiles = $this->print_responsefiles($USER->id, true);
90                 echo $OUTPUT->box($responsefiles, 'generalbox boxaligncenter');
91             }
92             return;
93         }
95         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id);
96         $item = $grading_info->items[0];
97         $grade = $item->grades[$USER->id];
99         if ($grade->hidden or $grade->grade === false) { // hidden or error
100             return;
101         }
103         if ($grade->grade === null and empty($grade->str_feedback)) {   /// Nothing to show yet
104             return;
105         }
107         $graded_date = $grade->dategraded;
108         $graded_by   = $grade->usermodified;
110     /// We need the teacher info
111         if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
112             print_error('cannotfindteacher');
113         }
115     /// Print the feedback
116         echo $OUTPUT->heading(get_string('submissionfeedback', 'assignment'), 3);
118         echo '<table cellspacing="0" class="feedback">';
120         echo '<tr>';
121         echo '<td class="left picture">';
122         echo $OUTPUT->user_picture($teacher);
123         echo '</td>';
124         echo '<td class="topic">';
125         echo '<div class="from">';
126         echo '<div class="fullname">'.fullname($teacher).'</div>';
127         echo '<div class="time">'.userdate($graded_date).'</div>';
128         echo '</div>';
129         echo '</td>';
130         echo '</tr>';
132         echo '<tr>';
133         echo '<td class="left side">&nbsp;</td>';
134         echo '<td class="content">';
135         if ($this->assignment->grade) {
136             echo '<div class="grade">';
137             echo get_string("grade").': '.$grade->str_long_grade;
138             echo '</div>';
139             echo '<div class="clearer"></div>';
140         }
142         echo '<div class="comment">';
143         echo $grade->str_feedback;
144         echo '</div>';
145         echo '</tr>';
147         echo '<tr>';
148         echo '<td class="left side">&nbsp;</td>';
149         echo '<td class="content">';
150         echo $this->print_responsefiles($USER->id, true);
151         echo '</tr>';
153         echo '</table>';
154     }
157     function view_upload_form() {
158         global $CFG, $USER;
160         $submission = $this->get_submission($USER->id);
162         if ($this->is_finalized($submission)) {
163             // no uploading
164             return;
165         }
167         if ($this->can_upload_file($submission)) {
168             $mform = new mod_assignment_upload_file_form('upload.php', $this);
169                         echo "<div class=\"uploadbox\">";
170                 $mform->display();
171             echo "</div>";
172         }
174     }
176     function view_notes() {
177         global $USER, $OUTPUT;
179         if ($submission = $this->get_submission($USER->id)
180           and !empty($submission->data1)) {
181             echo $OUTPUT->box(format_text($submission->data1, FORMAT_HTML), 'generalbox boxaligncenter boxwidthwide');
182         } else {
183             echo $OUTPUT->box(get_string('notesempty', 'assignment'), 'generalbox boxaligncenter');
184         }
185         if ($this->can_update_notes($submission)) {
186             $options = array ('id'=>$this->cm->id, 'action'=>'editnotes');
187             echo '<div style="text-align:center">';
188             echo $OUTPUT->single_button(new moodle_url('upload.php', $options), get_string('edit'));
189             echo '</div>';
190         }
191     }
193     function view_final_submission() {
194         global $CFG, $USER, $OUTPUT;
196         $submission = $this->get_submission($USER->id);
198         if ($this->isopen() and $this->can_finalize($submission)) {
199             //print final submit button
200             echo $OUTPUT->heading(get_string('submitformarking','assignment'), 3);
201             echo '<div style="text-align:center">';
202             echo '<form method="post" action="upload.php">';
203             echo '<fieldset class="invisiblefieldset">';
204             echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
205             echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
206             echo '<input type="hidden" name="action" value="finalize" />';
207             echo '<input type="submit" name="formarking" value="'.get_string('sendformarking', 'assignment').'" />';
208             echo '</fieldset>';
209             echo '</form>';
210             echo '</div>';
211         } else if (!$this->isopen()) {
212             echo $OUTPUT->heading(get_string('nomoresubmissions','assignment'), 3);
214         } else if ($this->drafts_tracked() and $state = $this->is_finalized($submission)) {
215             if ($state == ASSIGNMENT_STATUS_SUBMITTED) {
216                 echo $OUTPUT->heading(get_string('submitedformarking','assignment'), 3);
217             } else {
218                 echo $OUTPUT->heading(get_string('nomoresubmissions','assignment'), 3);
219             }
220         } else {
221             //no submission yet
222         }
223     }
226     /**
227      * Return true if var3 == hide description till available day
228      *
229      *@return boolean
230      */
231     function description_is_hidden() {
232         return ($this->assignment->var3 && (time() <= $this->assignment->timeavailable));
233     }
235     function custom_feedbackform($submission, $return=false) {
236         global $CFG;
238         $mode         = optional_param('mode', '', PARAM_ALPHA);
239         $offset       = optional_param('offset', 0, PARAM_INT);
240         $forcerefresh = optional_param('forcerefresh', 0, PARAM_BOOL);
242         $mform = new mod_assignment_upload_response_form("$CFG->wwwroot/mod/assignment/upload.php", $this);
244         $mform->set_data(array('id'=>$this->cm->id, 'offset'=>$offset, 'forcerefresh'=>$forcerefresh, 'userid'=>$submission->userid, 'mode'=>$mode));
246         $output = get_string('responsefiles', 'assignment').': ';
248         ob_start();
249         $mform->display();
250         $output = ob_get_clean();
252         if ($forcerefresh) {
253             $output .= $this->update_main_listing($submission);
254         }
256         $responsefiles = $this->print_responsefiles($submission->userid, true);
257         if (!empty($responsefiles)) {
258             $output .= $responsefiles;
259         }
261         if ($return) {
262             return $output;
263         }
264         echo $output;
265         return;
266     }
269     function print_student_answer($userid, $return=false){
270         global $CFG, $OUTPUT;
272         $submission = $this->get_submission($userid);
274         $output = '';
276         if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission)) {
277             $output .= '<strong>'.get_string('draft', 'assignment').':</strong> ';
278         }
280         if ($this->notes_allowed() and !empty($submission->data1)) {
281             $link = new moodle_url("/mod/assignment/type/upload/notes.php", array('id'=>$this->cm->id, 'userid'=>$userid));
282             $action = new popup_action('click', $link, 'notes', array('height' => 500, 'width' => 780));
283             $output .= $OUTPUT->action_link($link, get_string('notes', 'assignment'), $action, array('title'=>get_string('notes', 'assignment')));
285             $output .= '&nbsp;';
286         }
288         $fs = get_file_storage();
289         $browser = get_file_browser();
291         if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
293             foreach ($files as $file) {
294                 $filename = $file->get_filename();
295                 $found = true;
296                 $mimetype = $file->get_mimetype();
297                 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
298                 $output .= '<a href="'.$path.'" ><img class="icon" src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" alt="'.$mimetype.'" />'.s($filename).'</a>&nbsp;';
299             }
301         }
302         $output = '<div class="files">'.$output.'</div>';
303         $output .= '<br />';
305         return $output;
306     }
309     /**
310      * Produces a list of links to the files uploaded by a user
311      *
312      * @param $userid int optional id of the user. If 0 then $USER->id is used.
313      * @param $return boolean optional defaults to false. If true the list is returned rather than printed
314      * @return string optional
315      */
316     function print_user_files($userid=0, $return=false) {
317         global $CFG, $USER, $OUTPUT;
319         $mode    = optional_param('mode', '', PARAM_ALPHA);
320         $offset  = optional_param('offset', 0, PARAM_INT);
322         if (!$userid) {
323             if (!isloggedin()) {
324                 return '';
325             }
326             $userid = $USER->id;
327         }
329         $output = '';
331         $submission = $this->get_submission($userid);
333         $candelete = $this->can_delete_files($submission);
334         $strdelete   = get_string('delete');
336         if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission) and !empty($mode)) {                 // only during grading
337             $output .= '<strong>'.get_string('draft', 'assignment').':</strong><br />';
338         }
340         if ($this->notes_allowed() and !empty($submission->data1) and !empty($mode)) { // only during grading
342             $npurl = $CFG->wwwroot."/mod/assignment/type/upload/notes.php?id={$this->cm->id}&amp;userid=$userid&amp;offset=$offset&amp;mode=single";
343             $output .= '<a href="'.$npurl.'">'.get_string('notes', 'assignment').'</a><br />';
345         }
347         $fs = get_file_storage();
348         $browser = get_file_browser();
350         if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
351             $button = new portfolio_add_button();
352             foreach ($files as $file) {
353                 $filename = $file->get_filename();
354                 $mimetype = $file->get_mimetype();
355                 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
356                 $output .= '<a href="'.$path.'" ><img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />'.s($filename).'</a>';
358                 if ($candelete) {
359                     $delurl  = "$CFG->wwwroot/mod/assignment/delete.php?id={$this->cm->id}&amp;file=".rawurlencode($filename)."&amp;userid=$userid&amp;submissionid={$submission->id}&amp;mode=$mode&amp;offset=$offset";
361                     $output .= '<a href="'.$delurl.'">&nbsp;'
362                               .'<img title="'.$strdelete.'" src="'.$OUTPUT->pix_url('t/delete') . '" class="iconsmall" alt="" /></a> ';
363                 }
365                 if (has_capability('mod/assignment:exportownsubmission', $this->context)) {
366                     $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
367                     $button->set_format_by_file($file);
368                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
369                 }
370                 $output .= '<br />';
371             }
372             if (count($files) > 1 && has_capability('mod/assignment:exportownsubmission', $this->context)) {
373                 $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id), '/mod/assignment/locallib.php');
374                 $button->reset_formats(); // reset what we set before, since it's multi-file
375                 $output .= $button->to_html();
376             }
377         }
379         if ($this->drafts_tracked() and $this->isopen() and has_capability('mod/assignment:grade', $this->context) and $mode != '') { // we do not want it on view.php page
380             if ($this->can_unfinalize($submission)) {
381                 $options = array ('id'=>$this->cm->id, 'userid'=>$userid, 'action'=>'unfinalize', 'mode'=>$mode, 'offset'=>$offset);
382                 $output .= $OUTPUT->single_button(new moodle_url('upload.php', $options), get_string('unfinalize', 'assignment'));
383             } else if ($this->can_finalize($submission)) {
384                 $options = array ('id'=>$this->cm->id, 'userid'=>$userid, 'action'=>'finalizeclose', 'mode'=>$mode, 'offset'=>$offset);
385                 $output .= $OUTPUT->single_button(new moodle_url('upload.php', $options), get_string('finalize', 'assignment'));
386             }
387         }
389         $output = '<div class="files">'.$output.'</div>';
391         if ($return) {
392             return $output;
393         }
394         echo $output;
395     }
397     function print_responsefiles($userid, $return=false) {
398         global $CFG, $USER, $OUTPUT;
400         $mode    = optional_param('mode', '', PARAM_ALPHA);
401         $offset  = optional_param('offset', 0, PARAM_INT);
403         $output = '';
405         $candelete = $this->can_manage_responsefiles();
406         $strdelete   = get_string('delete');
408         $fs = get_file_storage();
409         $browser = get_file_browser();
411         if ($submission = $this->get_submission($userid)) {
412             if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'response', $submission->id, "timemodified", false)) {
413                 foreach ($files as $file) {
414                     $filename = $file->get_filename();
415                     $found = true;
416                     $mimetype = $file->get_mimetype();
417                     $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/response/'.$submission->id.'/'.$filename);
419                     $output .= '<a href="'.$path.'" ><img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" alt="'.$mimetype.'" />'.$filename.'</a>';
421                     if ($candelete) {
422                         $delurl  = "$CFG->wwwroot/mod/assignment/delete.php?id={$this->cm->id}&amp;file=".rawurlencode($filename)."&amp;userid=$userid&amp;mode=$mode&amp;offset=$offset&amp;action=response";
424                         $output .= '<a href="'.$delurl.'">&nbsp;'
425                                   .'<img title="'.$strdelete.'" src="'.$OUTPUT->pix_url('t/delete') . '" class="iconsmall" alt=""/></a> ';
426                     }
428                     $output .= '&nbsp;';
429                 }
431                 $output = '<div class="responsefiles">'.$output.'</div>';
432             }
433         }
435         if ($return) {
436             return $output;
437         }
438         echo $output;
439     }
442     function upload() {
443         $action = required_param('action', PARAM_ALPHA);
445         switch ($action) {
446             case 'finalize':
447                 $this->finalize();
448                 break;
449             case 'finalizeclose':
450                 $this->finalizeclose();
451                 break;
452             case 'unfinalize':
453                 $this->unfinalize();
454                 break;
455             case 'uploadresponse':
456                 $this->upload_responsefile();
457                 break;
458             case 'uploadfile':
459                 $this->upload_file();
460             case 'savenotes':
461             case 'editnotes':
462                 $this->upload_notes();
463             default:
464                 print_error('unknowuploadaction', '', '', $action);
465         }
466     }
468     function upload_notes() {
469         global $CFG, $USER, $OUTPUT, $DB;
471         $action = required_param('action', PARAM_ALPHA);
473         $returnurl = 'view.php?id='.$this->cm->id;
475         $mform = new mod_assignment_upload_notes_form();
477         $defaults = new object();
478         $defaults->id = $this->cm->id;
480         if ($submission = $this->get_submission($USER->id)) {
481             $defaults->text = clean_text($submission->data1);
482         } else {
483             $defaults->text = '';
484         }
486         $mform->set_data($defaults);
488         if ($mform->is_cancelled()) {
489             redirect('view.php?id='.$this->cm->id);
490         }
492         if (!$this->can_update_notes($submission)) {
493             $this->view_header(get_string('upload'));
494             echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
495             echo $OUTPUT->continue_button($returnurl);
496             $this->view_footer();
497             die;
498         }
500         if ($data = $mform->get_data() and $action == 'savenotes') {
501             $submission = $this->get_submission($USER->id, true); // get or create submission
502             $updated = new object();
503             $updated->id           = $submission->id;
504             $updated->timemodified = time();
505             $updated->data1        = $data->text;
507             if ($DB->update_record('assignment_submissions', $updated)) {
508                 add_to_log($this->course->id, 'assignment', 'upload', 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
509                 redirect($returnurl);
510                 $submission = $this->get_submission($USER->id);
511                 $this->update_grade($submission);
513             } else {
514                 $this->view_header(get_string('notes', 'assignment'));
515                 echo $OUTPUT->notification(get_string('notesupdateerror', 'assignment'));
516                 echo $OUTPUT->continue_button($returnurl);
517                 $this->view_footer();
518                 die;
519             }
520         }
522         /// show notes edit form
523         $this->view_header(get_string('notes', 'assignment'));
525         echo $OUTPUT->heading(get_string('notes', 'assignment'));
527         $mform->display();
529         $this->view_footer();
530         die;
531     }
533     function upload_responsefile() {
534         global $CFG, $USER, $OUTPUT, $PAGE;
536         $userid = required_param('userid', PARAM_INT);
537         $mode   = required_param('mode', PARAM_ALPHA);
538         $offset = required_param('offset', PARAM_INT);
540         $returnurl = "submissions.php?id={$this->cm->id}&amp;userid=$userid&amp;mode=$mode&amp;offset=$offset";
542         $mform = new mod_assignment_upload_response_form(null, $this);
543         if ($mform->get_data() and $this->can_manage_responsefiles()) {
544             $fs = get_file_storage();
545             $filename = $mform->get_new_filename('newfile');
546             if ($filename !== false) {
547                 $submission = $this->get_submission($userid, true, true);
548                 if (!$fs->file_exists($this->context->id, 'mod_assignment', 'response', $submission->id, '/', $filename)) {
549                     if ($file = $mform->save_stored_file('newfile', $this->context->id, 'mod_assignment', 'response', $submission->id, '/', $filename, false, $USER->id)) {
550                         redirect($returnurl);
551                     }
552                 }
553             }
554         }
555         $PAGE->set_title(get_string('upload'));
556         echo $OUTPUT->header();
557         echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
558         echo $OUTPUT->continue_button($returnurl);
559         echo $OUTPUT->footer();
560         die;
561     }
563     function upload_file() {
564         global $CFG, $USER, $DB, $OUTPUT;
566         $returnurl = 'view.php?id='.$this->cm->id;
568         $filecount = $this->count_user_files($USER->id);
569         $submission = $this->get_submission($USER->id);
571         if (!$this->can_upload_file($submission)) {
572             $this->view_header(get_string('upload'));
573             echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
574             echo $OUTPUT->continue_button($returnurl);
575             $this->view_footer();
576             die;
577         }
579         $mform = new mod_assignment_upload_file_form('upload.php', $this);
580         if ($mform->get_data()) {
581             $fs = get_file_storage();
582             $filename = $mform->get_new_filename('newfile');
583             if ($filename !== false) {
584                         $submission = $this->get_submission($USER->id, true); //create new submission if needed
585                 if (!$fs->file_exists($this->context->id, 'mod_assignment', 'submission', $submission->id, '/', $filename)) {
586                     if ($file = $mform->save_stored_file('newfile', $this->context->id, 'mod_assignment', 'submission', $submission->id, '/', $filename, false, $USER->id)) {
587                         $updates = new object();
588                         $updates->id = $submission->id;
589                         $updates->timemodified = time();
590                         if ($DB->update_record('assignment_submissions', $updates)) {
591                             add_to_log($this->course->id, 'assignment', 'upload',
592                                     'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
593                             $this->update_grade($submission);
594                             if (!$this->drafts_tracked()) {
595                                 $this->email_teachers($submission);
596                             }
597                             
598                             // Let Moodle know that an assessable file was uploaded (eg for plagiarism detection)
599                             $eventdata = new object();
600                             $eventdata->modulename   = 'assignment';
601                             $eventdata->cmid         = $this->cm->id;
602                             $eventdata->itemid       = $submission->id;
603                             $eventdata->courseid     = $this->course->id;
604                             $eventdata->userid       = $USER->id;
605                             $eventdata->file         = $file;
606                             events_trigger('assessable_file_uploaded', $eventdata);
608                             redirect('view.php?id='.$this->cm->id);
609                         } else {
610                             $file->delete();
611                         }
612                     }
613                 }
614             }
615         }
617         $this->view_header(get_string('upload'));
618         echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
619         echo $OUTPUT->continue_button($returnurl);
620         $this->view_footer();
621         die;
622     }
624     function send_file($filearea, $args) {
625         global $CFG, $DB, $USER;
626         require_once($CFG->libdir.'/filelib.php');
628         require_login($this->course, false, $this->cm);
630         if ($filearea === 'submission') {
631             $submissionid = (int)array_shift($args);
633             if (!$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'id'=>$submissionid))) {
634                 return false;
635             }
637             if ($USER->id != $submission->userid and !has_capability('mod/assignment:grade', $this->context)) {
638                 return false;
639             }
641             $relativepath = implode('/', $args);
642             $fullpath = "/{$this->context->id}/mod_assignment/submission/$submission->id/$relativepath";
644             $fs = get_file_storage();
645             if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
646                 return false;
647             }
648             send_stored_file($file, 0, 0, true); // download MUST be forced - security!
650         } else if ($filearea === 'response') {
651             $submissionid = (int)array_shift($args);
653             if (!$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'id'=>$submissionid))) {
654                 return false;
655             }
657             if ($USER->id != $submission->userid and !has_capability('mod/assignment:grade', $this->context)) {
658                 return false;
659             }
661             $relativepath = implode('/', $args);
662             $fullpath = "/{$this->context->id}/mod_assignment/response/$submission->id/$relativepath";
664             $fs = get_file_storage();
665             if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
666                 return false;
667             }
668             send_stored_file($file, 0, 0, true);
669         }
671         return false;
672     }
674     function finalize() {
675         global $USER, $DB, $OUTPUT;
677         $confirm    = optional_param('confirm', 0, PARAM_BOOL);
678         $returnurl  = 'view.php?id='.$this->cm->id;
679         $submission = $this->get_submission($USER->id);
681         if (!$this->can_finalize($submission)) {
682             redirect($returnurl); // probably already graded, redirect to assignment page, the reason should be obvious
683         }
685         if (!data_submitted() or !$confirm or !confirm_sesskey()) {
686             $optionsno = array('id'=>$this->cm->id);
687             $optionsyes = array ('id'=>$this->cm->id, 'confirm'=>1, 'action'=>'finalize', 'sesskey'=>sesskey());
688             $this->view_header(get_string('submitformarking', 'assignment'));
689             echo $OUTPUT->heading(get_string('submitformarking', 'assignment'));
690             echo $OUTPUT->confirm(get_string('onceassignmentsent', 'assignment'), new moodle_url('upload.php', $optionsyes),new moodle_url( 'view.php', $optionsno));
691             $this->view_footer();
692             die;
694         }
695         $updated = new object();
696         $updated->id           = $submission->id;
697         $updated->data2        = ASSIGNMENT_STATUS_SUBMITTED;
698         $updated->timemodified = time();
700         if ($DB->update_record('assignment_submissions', $updated)) {
701             add_to_log($this->course->id, 'assignment', 'upload', //TODO: add finalize action to log
702                     'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
703             $submission = $this->get_submission($USER->id);
704             $this->update_grade($submission);
705             $this->email_teachers($submission);
706         } else {
707             $this->view_header(get_string('submitformarking', 'assignment'));
708             echo $OUTPUT->notification(get_string('finalizeerror', 'assignment'));
709             echo $OUTPUT->continue_button($returnurl);
710             $this->view_footer();
711             die;
712         }
714         // Trigger assessable_files_done event to show files are complete
715         $eventdata = new object();
716         $eventdata->modulename   = 'assignment';
717         $eventdata->cmid         = $this->cm->id;
718         $eventdata->itemid       = $submission->id;
719         $eventdata->courseid     = $this->course->id;
720         $eventdata->userid       = $USER->id;
721         events_trigger('assessable_files_done', $eventdata);
723         redirect($returnurl);
724     }
726     function finalizeclose() {
727         global $DB;
729         $userid    = optional_param('userid', 0, PARAM_INT);
730         $mode      = required_param('mode', PARAM_ALPHA);
731         $offset    = required_param('offset', PARAM_INT);
732         $returnurl = "submissions.php?id={$this->cm->id}&amp;userid=$userid&amp;mode=$mode&amp;offset=$offset&amp;forcerefresh=1";
734         // create but do not add student submission date
735         $submission = $this->get_submission($userid, true, true);
737         if (!data_submitted() or !$this->can_finalize($submission) or !confirm_sesskey()) {
738             redirect($returnurl); // probably closed already
739         }
741         $updated = new object();
742         $updated->id    = $submission->id;
743         $updated->data2 = ASSIGNMENT_STATUS_CLOSED;
745         if ($DB->update_record('assignment_submissions', $updated)) {
746             add_to_log($this->course->id, 'assignment', 'upload', //TODO: add finalize action to log
747                     'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
748             $submission = $this->get_submission($userid, false, true);
749             $this->update_grade($submission);
750         }
751         redirect($returnurl);
752     }
754     function unfinalize() {
755         global $DB;
757         $userid = required_param('userid', PARAM_INT);
758         $mode   = required_param('mode', PARAM_ALPHA);
759         $offset = required_param('offset', PARAM_INT);
761         $returnurl = "submissions.php?id={$this->cm->id}&amp;userid=$userid&amp;mode=$mode&amp;offset=$offset&amp;forcerefresh=1";
763         if (data_submitted()
764           and $submission = $this->get_submission($userid)
765           and $this->can_unfinalize($submission)
766           and confirm_sesskey()) {
768             $updated = new object();
769             $updated->id = $submission->id;
770             $updated->data2 = '';
771             if ($DB->update_record('assignment_submissions', $updated)) {
772                 //TODO: add unfinalize action to log
773                 add_to_log($this->course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
774                 $submission = $this->get_submission($userid);
775                 $this->update_grade($submission);
776             } else {
777                 $this->view_header(get_string('submitformarking', 'assignment'));
778                 echo $OUTPUT->notification(get_string('unfinalizeerror', 'assignment'));
779                 echo $OUTPUT->continue_button($returnurl);
780                 $this->view_footer();
781                 die;
782             }
783         }
784         redirect($returnurl);
785     }
788     function delete() {
789         $action   = optional_param('action', '', PARAM_ALPHA);
791         switch ($action) {
792             case 'response':
793                 $this->delete_responsefile();
794                 break;
795             default:
796                 $this->delete_file();
797         }
798         die;
799     }
802     function delete_responsefile() {
803         global $CFG, $OUTPUT,$PAGE;
805         $file     = required_param('file', PARAM_FILE);
806         $userid   = required_param('userid', PARAM_INT);
807         $mode     = required_param('mode', PARAM_ALPHA);
808         $offset   = required_param('offset', PARAM_INT);
809         $confirm  = optional_param('confirm', 0, PARAM_BOOL);
811         $returnurl = "submissions.php?id={$this->cm->id}&userid=$userid&mode=$mode&offset=$offset";
813         if (!$this->can_manage_responsefiles()) {
814            redirect($returnurl);
815         }
817         $urlreturn = 'submissions.php';
818         $optionsreturn = array('id'=>$this->cm->id, 'offset'=>$offset, 'mode'=>$mode, 'userid'=>$userid);
820         if (!data_submitted() or !$confirm or !confirm_sesskey()) {
821             $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'action'=>'response', 'mode'=>$mode, 'offset'=>$offset, 'sesskey'=>sesskey());
822             $PAGE->set_title(get_string('delete'));
823             echo $OUTPUT->header();
824             echo $OUTPUT->heading(get_string('delete'));
825             echo $OUTPUT->confirm(get_string('confirmdeletefile', 'assignment', $file), new moodle_url('delete.php', $optionsyes), new moodle_url($urlreturn, $optionsreturn));
826             echo $OUTPUT->footer();
827             die;
828         }
830         if ($submission = $this->get_submission($userid)) {
831             $fs = get_file_storage();
832             if ($file = $fs->get_file($this->context->id, 'mod_assignment', 'response', $submission->id, '/', $file)) {
833                 $file->delete();
834             }
835         }
836         redirect($returnurl);
837     }
840     function delete_file() {
841         global $CFG, $DB, $OUTPUT, $PAGE;
843         $file     = required_param('file', PARAM_FILE);
844         $userid   = required_param('userid', PARAM_INT);
845         $confirm  = optional_param('confirm', 0, PARAM_BOOL);
846         $mode     = optional_param('mode', '', PARAM_ALPHA);
847         $offset   = optional_param('offset', 0, PARAM_INT);
849         require_login($this->course->id, false, $this->cm);
851         if (empty($mode)) {
852             $urlreturn = 'view.php';
853             $optionsreturn = array('id'=>$this->cm->id);
854             $returnurl = 'view.php?id='.$this->cm->id;
855         } else {
856             $urlreturn = 'submissions.php';
857             $optionsreturn = array('id'=>$this->cm->id, 'offset'=>$offset, 'mode'=>$mode, 'userid'=>$userid);
858             $returnurl = "submissions.php?id={$this->cm->id}&offset=$offset&mode=$mode&userid=$userid";
859         }
861         if (!$submission = $this->get_submission($userid) // incorrect submission
862           or !$this->can_delete_files($submission)) {     // can not delete
863             $this->view_header(get_string('delete'));
864             echo $OUTPUT->notification(get_string('cannotdeletefiles', 'assignment'));
865             echo $OUTPUT->continue_button($returnurl);
866             $this->view_footer();
867             die;
868         }
870         if (!data_submitted() or !$confirm or !confirm_sesskey()) {
871             $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'sesskey'=>sesskey(), 'mode'=>$mode, 'offset'=>$offset, 'sesskey'=>sesskey());
872             if (empty($mode)) {
873                 $this->view_header(get_string('delete'));
874             } else {
875                 $PAGE->set_title(get_string('delete'));
876                 echo $OUTPUT->header();
877             }
878             echo $OUTPUT->heading(get_string('delete'));
879             echo $OUTPUT->confirm(get_string('confirmdeletefile', 'assignment', $file), new moodle_url('delete.php', $optionsyes), new moodle_url($urlreturn, $optionsreturn));
880             if (empty($mode)) {
881                 $this->view_footer();
882             } else {
883                 echo $OUTPUT->footer();
884             }
885             die;
886         }
888         $fs = get_file_storage();
889         if ($file = $fs->get_file($this->context->id, 'mod_assignment', 'submission', $submission->id, '/', $file)) {
890             $file->delete();
891             $submission->timemodified = time();
892             $DB->update_record('assignment_submissions', $submission);
893             add_to_log($this->course->id, 'assignment', 'upload', //TODO: add delete action to log
894                     'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
895             $this->update_grade($submission);
896         }
897         redirect($returnurl);
898     }
901     function can_upload_file($submission) {
902         global $USER;
904         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
905           and $this->isopen()                                                 // assignment not closed yet
906           and (empty($submission) or $submission->userid == $USER->id)        // his/her own submission
907           and $this->count_user_files($USER->id) < $this->assignment->var1    // file limit not reached
908           and !$this->is_finalized($submission)) {                            // no uploading after final submission
909             return true;
910         } else {
911             return false;
912         }
913     }
915     function can_manage_responsefiles() {
916         if (has_capability('mod/assignment:grade', $this->context)) {
917             return true;
918         } else {
919             return false;
920         }
921     }
923     function can_delete_files($submission) {
924         global $USER;
926         if (has_capability('mod/assignment:grade', $this->context)) {
927             return true;
928         }
930         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
931           and $this->isopen()                                      // assignment not closed yet
932           and $this->assignment->resubmit                          // deleting allowed
933           and $USER->id == $submission->userid                     // his/her own submission
934           and !$this->is_finalized($submission)) {                 // no deleting after final submission
935             return true;
936         } else {
937             return false;
938         }
939     }
941     function drafts_tracked() {
942         return !empty($this->assignment->var4);
943     }
945     /**
946      * Returns submission status
947      * @param object $submission - may be empty
948      * @return string submission state - empty, ASSIGNMENT_STATUS_SUBMITTED or ASSIGNMENT_STATUS_CLOSED
949      */
950     function is_finalized($submission) {
951         if (!$this->drafts_tracked()) {
952             return '';
954         } else if (empty($submission)) {
955             return '';
957         } else if ($submission->data2 == ASSIGNMENT_STATUS_SUBMITTED or $submission->data2 == ASSIGNMENT_STATUS_CLOSED) {
958             return $submission->data2;
960         } else {
961             return '';
962         }
963     }
965     function can_unfinalize($submission) {
966         if (!$this->drafts_tracked()) {
967             return false;
968         }
969         if (has_capability('mod/assignment:grade', $this->context)
970           and $this->isopen()
971           and $this->is_finalized($submission)) {
972             return true;
973         } else {
974             return false;
975         }
976     }
978     function can_finalize($submission) {
979         global $USER;
980         if (!$this->drafts_tracked()) {
981             return false;
982         }
984         if ($this->is_finalized($submission)) {
985             return false;
986         }
988         if (has_capability('mod/assignment:grade', $this->context)) {
989             return true;
991         } else if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
992           and $this->isopen()                                                 // assignment not closed yet
993           and !empty($submission)                                             // submission must exist
994           and $submission->userid == $USER->id                                // his/her own submission
995           and ($this->count_user_files($USER->id)
996             or ($this->notes_allowed() and !empty($submission->data1)))) {    // something must be submitted
998             return true;
999         } else {
1000             return false;
1001         }
1002     }
1004     function can_update_notes($submission) {
1005         global $USER;
1007         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
1008           and $this->notes_allowed()                                          // notesd must be allowed
1009           and $this->isopen()                                                 // assignment not closed yet
1010           and (empty($submission) or $USER->id == $submission->userid)        // his/her own submission
1011           and !$this->is_finalized($submission)) {                            // no updateingafter final submission
1012             return true;
1013         } else {
1014             return false;
1015         }
1016     }
1018     function notes_allowed() {
1019         return (boolean)$this->assignment->var2;
1020     }
1022     function count_responsefiles($userid) {
1023         if ($submission = $this->get_submission($userid)) {
1024             $fs = get_file_storage();
1025             $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'response', $submission->id, "id", false);
1026             return count($files);
1027         } else {
1028             return 0;
1029         }
1030     }
1032     function setup_elements(&$mform) {
1033         global $CFG, $COURSE;
1035         $ynoptions = array( 0 => get_string('no'), 1 => get_string('yes'));
1037         $choices = get_max_upload_sizes($CFG->maxbytes, $COURSE->maxbytes);
1038         $choices[0] = get_string('courseuploadlimit') . ' ('.display_size($COURSE->maxbytes).')';
1039         $mform->addElement('select', 'maxbytes', get_string('maximumsize', 'assignment'), $choices);
1040         $mform->setDefault('maxbytes', $CFG->assignment_maxbytes);
1042         $mform->addElement('select', 'resubmit', get_string('allowdeleting', 'assignment'), $ynoptions);
1043         $mform->addHelpButton('resubmit', 'allowdeleting', 'assignment');
1044         $mform->setDefault('resubmit', 1);
1046         $options = array();
1047         for($i = 1; $i <= 20; $i++) {
1048             $options[$i] = $i;
1049         }
1050         $mform->addElement('select', 'var1', get_string('allowmaxfiles', 'assignment'), $options);
1051         $mform->addHelpButton('var1', 'allowmaxfiles', 'assignment');
1052         $mform->setDefault('var1', 3);
1054         $mform->addElement('select', 'var2', get_string('allownotes', 'assignment'), $ynoptions);
1055         $mform->addHelpButton('var2', 'allownotes', 'assignment');
1056         $mform->setDefault('var2', 0);
1058         $mform->addElement('select', 'var3', get_string('hideintro', 'assignment'), $ynoptions);
1059         $mform->addHelpButton('var3', 'hideintro', 'assignment');
1060         $mform->setDefault('var3', 0);
1062         $mform->addElement('select', 'emailteachers', get_string('emailteachers', 'assignment'), $ynoptions);
1063         $mform->addHelpButton('emailteachers', 'emailteachers', 'assignment');
1064         $mform->setDefault('emailteachers', 0);
1066         $mform->addElement('select', 'var4', get_string('trackdrafts', 'assignment'), $ynoptions);
1067         $mform->addHelpButton('var4', 'trackdrafts', 'assignment');
1068         $mform->setDefault('var4', 1);
1070     }
1072     function portfolio_exportable() {
1073         return true;
1074     }
1076     function extend_settings_navigation($node) {
1077         global $CFG, $USER, $OUTPUT;
1079         // get users submission if there is one
1080         $submission = $this->get_submission();
1081         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')) {
1082             $editable = $this->isopen() && (!$submission || $this->assignment->resubmit || !$submission->timemarked);
1083         } else {
1084             $editable = false;
1085         }
1087         // If the user has submitted something add a bit more stuff
1088         if ($submission) {
1089             // Add a view link to the settings nav
1090             $link = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
1091             $node->add(get_string('viewmysubmission', 'assignment'), $link, navigation_node::TYPE_SETTING);
1092             if (!empty($submission->timemodified)) {
1093                 $submittednode = $node->add(get_string('submitted', 'assignment') . ' ' . userdate($submission->timemodified));
1094                 $submittednode->text = preg_replace('#([^,])\s#', '$1&nbsp;', $submittednode->text);
1095                 $submittednode->add_class('note');
1096                 if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
1097                     $submittednode->add_class('early');
1098                 } else {
1099                     $submittednode->add_class('late');
1100                 }
1101             }
1102         }
1104         // Check if the user has uploaded any files, if so we can add some more stuff to the settings nav
1105         if ($submission && is_enrolled($this->context, $USER, 'mod/assignment:submit') && $this->count_user_files($USER->id)) {
1106             $fs = get_file_storage();
1107             if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
1108                 if (!$this->drafts_tracked() or !$this->isopen() or $this->is_finalized($submission)) {
1109                     $filenode = $node->add(get_string('submission', 'assignment'));
1110                 } else {
1111                     $filenode = $node->add(get_string('submissiondraft', 'assignment'));
1112                 }
1113                 foreach ($files as $file) {
1114                     $filename = $file->get_filename();
1115                     $mimetype = $file->get_mimetype();
1116                     $link = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
1117                     $filenode->add($filename, $link, navigation_node::TYPE_SETTING, null, null, new pix_icon(file_mimetype_icon($mimetype),''));
1118                 }
1119             }
1120         }
1122         // Show a notes link if they are enabled
1123         if ($this->notes_allowed()) {
1124             $link = new moodle_url('/mod/assignment/upload.php', array('id'=>$this->cm->id, 'action'=>'editnotes', 'sesskey'=>sesskey()));
1125             $node->add(get_string('notes', 'assignment'), $link);
1126         }
1127     }
1129     /**
1130      * creates a zip of all assignment submissions and sends a zip to the browser
1131      */
1132     public function download_submissions() {
1133         global $CFG,$DB;
1134         require_once($CFG->libdir.'/filelib.php');
1135         $submissions = $this->get_submissions('','');
1136         if (empty($submissions)) {
1137             error("there are no submissions to download");
1138         }
1139         $filesforzipping = array();
1140         $filenewname = clean_filename($this->assignment->name); //create prefix of individual files
1141         $fs = get_file_storage();
1143         $groupmode = groupmode($this->course,$this->cm);
1144         $groupid = 0;   // All users
1145         $groupname = '';
1146         if($groupmode) {
1147             $group = get_current_group($this->course->id, true);
1148             $groupid = $group->id;
1149             $groupname = $group->name.'-';
1150         }
1151         $filename = str_replace(' ', '_', clean_filename($this->course->shortname.'-'.$this->assignment->name.'-'.$groupname.$this->assignment->id.".zip")); //name of new zip file.
1152         foreach ($submissions as $submission) {
1153             $a_userid = $submission->userid; //get userid
1154             if ((groups_is_member($groupid,$a_userid)or !$groupmode or !$groupid)) {
1155                 $a_assignid = $submission->assignment; //get name of this assignment for use in the file names.
1156                 $a_user = $DB->get_record("user", array("id"=>$a_userid),'id,username,firstname,lastname'); //get user firstname/lastname
1158                 $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
1159                 foreach ($files as $file) {
1160                     //get files new name.
1161                     $fileforzipname =  $a_user->username . "_" . $filenewname . "_" . $file->get_filename();
1162                     //save file name to array for zipping.
1163                     $filesforzipping[$fileforzipname] = $file;
1164                 }
1165             }
1166         } // end of foreach loop
1167         if ($zipfile = assignment_pack_files($filesforzipping)) {
1168             send_temp_file($zipfile, $filename); //send file and delete after sending.
1169         }
1170     }
1173 class mod_assignment_upload_notes_form extends moodleform {
1175     function get_data() {
1176         $data = parent::get_data();
1177         if ($data) {
1178             $data->format = $data->text['format'];
1179             $data->text = $data->text['text'];
1180         }
1181         return $data;
1182     }
1184     function set_data($data) {
1185         if (!isset($data->format)) {
1186             $data->format = FORMAT_HTML;
1187         }
1188         if (isset($data->text)) {
1189             $data->text = array('text'=>$data->text, 'format'=>$data->format);
1190         }
1191         parent::set_data($data);
1192     }
1194     function definition() {
1195         $mform = $this->_form;
1197         // visible elements
1198         $mform->addElement('editor', 'text', get_string('notes', 'assignment'), null, null);
1199         $mform->setType('text', PARAM_RAW); // to be cleaned before display
1201         // hidden params
1202         $mform->addElement('hidden', 'id', 0);
1203         $mform->setType('id', PARAM_INT);
1204         $mform->addElement('hidden', 'action', 'savenotes');
1205         $mform->setType('action', PARAM_ALPHA);
1207         // buttons
1208         $this->add_action_buttons();
1209     }
1212 class mod_assignment_upload_response_form extends moodleform {
1213     function definition() {
1214         $mform = $this->_form;
1215         $instance = $this->_customdata;
1217         // visible elements
1218         $mform->addElement('file', 'newfile', get_string('uploadafile'));
1220         // hidden params
1221         $mform->addElement('hidden', 'id', $instance->cm->id);
1222         $mform->setType('id', PARAM_INT);
1223         $mform->addElement('hidden', 'action', 'uploadresponse');
1224         $mform->setType('action', PARAM_ALPHA);
1225         $mform->addElement('hidden', 'mode');
1226         $mform->setType('mode', PARAM_ALPHA);
1227         $mform->addElement('hidden', 'offset');
1228         $mform->setType('offset', PARAM_INT);
1229         $mform->addElement('hidden', 'forcerefresh');
1230         $mform->setType('forcerefresh', PARAM_INT);
1231         $mform->addElement('hidden', 'userid');
1232         $mform->setType('userid', PARAM_INT);
1234         // buttons
1235         $this->add_action_buttons(false, get_string('uploadthisfile'));
1236     }