Merge branch 'MDL-29400_misleading_file_upload_status' of git://github.com/gerrywasta...
[moodle.git] / mod / assignment / type / upload / assignment.class.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Assignment upload type implementation
20  *
21  * @package   mod-assignment
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 require_once(dirname(__FILE__).'/upload_form.php');
26 require_once($CFG->libdir . '/portfoliolib.php');
27 require_once($CFG->dirroot . '/mod/assignment/lib.php');
29 define('ASSIGNMENT_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
30 define('ASSIGNMENT_STATUS_CLOSED', 'closed');       // teacher prevents more submissions
32 /**
33  * Extend the base assignment class for assignments where you upload a single file
34  *
35  */
36 class assignment_upload extends assignment_base {
38     function assignment_upload($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
39         parent::assignment_base($cmid, $assignment, $cm, $course);
40         $this->type = 'upload';
41     }
43     function view() {
44         global $USER, $OUTPUT;
46         require_capability('mod/assignment:view', $this->context);
47         $cansubmit = has_capability('mod/assignment:submit', $this->context);
49         add_to_log($this->course->id, 'assignment', 'view', "view.php?id={$this->cm->id}", $this->assignment->id, $this->cm->id);
51         $this->view_header();
53         if ($this->assignment->timeavailable > time()
54           and !has_capability('mod/assignment:grade', $this->context)      // grading user can see it anytime
55           and $this->assignment->var3) {                                   // force hiding before available date
56             echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
57             print_string('notavailableyet', 'assignment');
58             echo $OUTPUT->box_end();
59         } else {
60             $this->view_intro();
61         }
63         $this->view_dates();
65         if (is_enrolled($this->context, $USER)) {
66             if ($submission = $this->get_submission($USER->id)) {
67                 $filecount = $this->count_user_files($submission->id);
68             } else {
69                 $filecount = 0;
70             }
71             if ($cansubmit or !empty($filecount)) { //if a user has submitted files using a previous role we should still show the files
72                 $this->view_feedback();
74                 if (!$this->drafts_tracked() or !$this->isopen() or $this->is_finalized($submission)) {
75                     echo $OUTPUT->heading(get_string('submission', 'assignment'), 3);
76                 } else {
77                     echo $OUTPUT->heading(get_string('submissiondraft', 'assignment'), 3);
78                 }
80                 if ($filecount and $submission) {
81                     echo $OUTPUT->box($this->print_user_files($USER->id, true), 'generalbox boxaligncenter', 'userfiles');
82                 } else {
83                     if (!$this->isopen() or $this->is_finalized($submission)) {
84                         echo $OUTPUT->box(get_string('nofiles', 'assignment'), 'generalbox boxaligncenter nofiles', 'userfiles');
85                     } else {
86                         echo $OUTPUT->box(get_string('nofilesyet', 'assignment'), 'generalbox boxaligncenter nofiles', 'userfiles');
87                     }
88                 }
90                 $this->view_upload_form();
92                 if ($this->notes_allowed()) {
93                     echo $OUTPUT->heading(get_string('notes', 'assignment'), 3);
94                     $this->view_notes();
95                 }
97                 $this->view_final_submission();
98             }
99         }
100         $this->view_footer();
101     }
104     function view_feedback($submission=NULL) {
105         global $USER, $CFG, $DB, $OUTPUT, $PAGE;
106         require_once($CFG->libdir.'/gradelib.php');
107         require_once("$CFG->dirroot/grade/grading/lib.php");
109         if (!$submission) { /// Get submission for this assignment
110             $userid = $USER->id;
111             $submission = $this->get_submission($userid);
112         } else {
113             $userid = $submission->userid;
114         }
116         if (empty($submission->timemarked)) {   /// Nothing to show, so print nothing
117             return;
118         }
119         // Check the user can submit
120         $canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
121         // If not then check if the user still has the view cap and has a previous submission
122         $canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
123         // Or if user can grade (is a teacher or admin)
124         $canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
126         if (!$canviewfeedback) {
127             // can not view or submit assignments -> no feedback
128             return;
129         }
131         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
132         $item = $grading_info->items[0];
133         $grade = $item->grades[$userid];
135         if ($grade->hidden or $grade->grade === false) { // hidden or error
136             return;
137         }
139         if ($grade->grade === null and empty($grade->str_feedback)) {   // No grade to show yet
140             if ($this->count_responsefiles($userid)) {   // but possibly response files are present
141                 echo $OUTPUT->heading(get_string('responsefiles', 'assignment'), 3);
142                 $responsefiles = $this->print_responsefiles($userid, true);
143                 echo $OUTPUT->box($responsefiles, 'generalbox boxaligncenter');
144             }
145             return;
146         }
148         $graded_date = $grade->dategraded;
149         $graded_by   = $grade->usermodified;
151     /// We need the teacher info
152         if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
153             print_error('cannotfindteacher');
154         }
156     /// Print the feedback
157         echo $OUTPUT->heading(get_string('submissionfeedback', 'assignment'), 3);
159         echo '<table cellspacing="0" class="feedback">';
161         echo '<tr>';
162         echo '<td class="left picture">';
163         echo $OUTPUT->user_picture($teacher);
164         echo '</td>';
165         echo '<td class="topic">';
166         echo '<div class="from">';
167         echo '<div class="fullname">'.fullname($teacher).'</div>';
168         echo '<div class="time">'.userdate($graded_date).'</div>';
169         echo '</div>';
170         echo '</td>';
171         echo '</tr>';
173         echo '<tr>';
174         echo '<td class="left side">&nbsp;</td>';
175         echo '<td class="content">';
176         $gradestr = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
177         if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
178             $controller->set_grade_range(make_grades_menu($this->assignment->grade));
179             echo $controller->render_grade($PAGE, $submission->id, $item, $gradestr, has_capability('mod/assignment:grade', $this->context));
180         } else {
181             echo $gradestr;
182         }
183         echo '<div class="clearer"></div>';
185         echo '<div class="comment">';
186         echo $grade->str_feedback;
187         echo '</div>';
188         echo '</tr>';
190         echo '<tr>';
191         echo '<td class="left side">&nbsp;</td>';
192         echo '<td class="content">';
193         echo $this->print_responsefiles($userid, true);
194         echo '</tr>';
196         echo '</table>';
197     }
200     function view_upload_form() {
201         global $CFG, $USER, $OUTPUT;
203         $submission = $this->get_submission($USER->id);
205         if ($this->is_finalized($submission)) {
206             // no uploading
207             return;
208         }
210         if ($this->can_upload_file($submission)) {
211             $fs = get_file_storage();
212             // edit files in another page
213             if ($submission) {
214                 if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
215                     $str = get_string('editthesefiles', 'assignment');
216                 } else {
217                     $str = get_string('uploadfiles', 'assignment');
218                 }
219             } else {
220                 $str = get_string('uploadfiles', 'assignment');
221             }
222             echo $OUTPUT->single_button(new moodle_url('/mod/assignment/type/upload/upload.php', array('contextid'=>$this->context->id, 'userid'=>$USER->id)), $str, 'get');
223         }
224     }
226     function view_notes() {
227         global $USER, $OUTPUT;
229         if ($submission = $this->get_submission($USER->id)
230           and !empty($submission->data1)) {
231             echo $OUTPUT->box(format_text($submission->data1, FORMAT_HTML, array('overflowdiv'=>true)), 'generalbox boxaligncenter boxwidthwide');
232         } else {
233             echo $OUTPUT->box(get_string('notesempty', 'assignment'), 'generalbox boxaligncenter');
234         }
235         if ($this->can_update_notes($submission)) {
236             $options = array ('id'=>$this->cm->id, 'action'=>'editnotes');
237             echo '<div style="text-align:center">';
238             echo $OUTPUT->single_button(new moodle_url('upload.php', $options), get_string('edit'));
239             echo '</div>';
240         }
241     }
243     function view_final_submission() {
244         global $CFG, $USER, $OUTPUT;
246         $submission = $this->get_submission($USER->id);
248         if ($this->isopen() and $this->can_finalize($submission)) {
249             //print final submit button
250             echo $OUTPUT->heading(get_string('submitformarking','assignment'), 3);
251             echo '<div style="text-align:center">';
252             echo '<form method="post" action="upload.php">';
253             echo '<fieldset class="invisiblefieldset">';
254             echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
255             echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
256             echo '<input type="hidden" name="action" value="finalize" />';
257             echo '<input type="submit" name="formarking" value="'.get_string('sendformarking', 'assignment').'" />';
258             echo '</fieldset>';
259             echo '</form>';
260             echo '</div>';
261         } else if (!$this->isopen()) {
262             echo $OUTPUT->heading(get_string('nomoresubmissions','assignment'), 3);
264         } else if ($this->drafts_tracked() and $state = $this->is_finalized($submission)) {
265             if ($state == ASSIGNMENT_STATUS_SUBMITTED) {
266                 echo $OUTPUT->heading(get_string('submitedformarking','assignment'), 3);
267             } else {
268                 echo $OUTPUT->heading(get_string('nomoresubmissions','assignment'), 3);
269             }
270         } else {
271             //no submission yet
272         }
273     }
276     /**
277      * Return true if var3 == hide description till available day
278      *
279      *@return boolean
280      */
281     function description_is_hidden() {
282         return ($this->assignment->var3 && (time() <= $this->assignment->timeavailable));
283     }
285     function print_student_answer($userid, $return=false){
286         global $CFG, $OUTPUT, $PAGE;
288         $submission = $this->get_submission($userid);
290         $output = '';
292         if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission)) {
293             $output .= '<strong>'.get_string('draft', 'assignment').':</strong> ';
294         }
296         if ($this->notes_allowed() and !empty($submission->data1)) {
297             $link = new moodle_url("/mod/assignment/type/upload/notes.php", array('id'=>$this->cm->id, 'userid'=>$userid));
298             $action = new popup_action('click', $link, 'notes', array('height' => 500, 'width' => 780));
299             $output .= $OUTPUT->action_link($link, get_string('notes', 'assignment'), $action, array('title'=>get_string('notes', 'assignment')));
301             $output .= '&nbsp;';
302         }
305         $renderer = $PAGE->get_renderer('mod_assignment');
306         $output = $OUTPUT->box_start('files').$output;
307         $output .= $renderer->assignment_files($this->context, $submission->id);
308         $output .= $OUTPUT->box_end();
310         return $output;
311     }
314     /**
315      * Produces a list of links to the files uploaded by a user
316      *
317      * @param $userid int optional id of the user. If 0 then $USER->id is used.
318      * @param $return boolean optional defaults to false. If true the list is returned rather than printed
319      * @return string optional
320      */
321     function print_user_files($userid=0, $return=false) {
322         global $CFG, $USER, $OUTPUT, $PAGE;
324         $mode    = optional_param('mode', '', PARAM_ALPHA);
325         $offset  = optional_param('offset', 0, PARAM_INT);
327         if (!$userid) {
328             if (!isloggedin()) {
329                 return '';
330             }
331             $userid = $USER->id;
332         }
334         $output = $OUTPUT->box_start('files');
336         $submission = $this->get_submission($userid);
338         // only during grading
339         if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission) and !empty($mode)) {
340             $output .= '<strong>'.get_string('draft', 'assignment').':</strong><br />';
341         }
343         if ($this->notes_allowed() and !empty($submission->data1) and !empty($mode)) { // only during grading
345             $npurl = $CFG->wwwroot."/mod/assignment/type/upload/notes.php?id={$this->cm->id}&amp;userid=$userid&amp;offset=$offset&amp;mode=single";
346             $output .= '<a href="'.$npurl.'">'.get_string('notes', 'assignment').'</a><br />';
348         }
350         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
351             if ($this->can_unfinalize($submission)) {
352                 //$options = array ('id'=>$this->cm->id, 'userid'=>$userid, 'action'=>'unfinalize', 'mode'=>$mode, 'offset'=>$offset);
353                 $output .= '<br /><input type="submit" name="unfinalize" value="'.get_string('unfinalize', 'assignment').'" />';
354                 $output .=  $OUTPUT->help_icon('unfinalize', 'assignment');
356             } else if ($this->can_finalize($submission)) {
357                 //$options = array ('id'=>$this->cm->id, 'userid'=>$userid, 'action'=>'finalizeclose', 'mode'=>$mode, 'offset'=>$offset);
358                 $output .= '<br /><input type="submit" name="finalize" value="'.get_string('finalize', 'assignment').'" />';
359             }
360         }
362         if ($submission) {
363             $renderer = $PAGE->get_renderer('mod_assignment');
364             $output .= $renderer->assignment_files($this->context, $submission->id);
365         }
366         $output .= $OUTPUT->box_end();
368         if ($return) {
369             return $output;
370         }
371         echo $output;
372     }
374     function submissions($mode) {
375         // redirects out of form to process (un)finalizing.
376         $unfinalize = optional_param('unfinalize', FALSE, PARAM_TEXT);
377         $finalize = optional_param('finalize', FALSE, PARAM_TEXT);
378         if ($unfinalize) {
379             $this->unfinalize('single');
380         } else if ($finalize) {
381             $this->finalize('single');
382         }
383         if ($unfinalize || $finalize) {
384             $mode = 'singlenosave';
385         }
386         parent::submissions($mode);
387     }
389     function process_feedback() {
390         if (!$feedback = data_submitted() or !confirm_sesskey()) {      // No incoming data?
391             return false;
392         }
393         $userid = required_param('userid', PARAM_INT);
394         $offset = required_param('offset', PARAM_INT);
395         $mform = $this->display_submission($offset, $userid, false);
396         parent::process_feedback($mform);
397     }
399     function print_responsefiles($userid, $return=false) {
400         global $CFG, $USER, $OUTPUT, $PAGE;
402         $mode    = optional_param('mode', '', PARAM_ALPHA);
403         $offset  = optional_param('offset', 0, PARAM_INT);
405         $output = $OUTPUT->box_start('responsefiles');
407         $candelete = $this->can_manage_responsefiles();
408         $strdelete   = get_string('delete');
410         $fs = get_file_storage();
411         $browser = get_file_browser();
413         if ($submission = $this->get_submission($userid)) {
414             $renderer = $PAGE->get_renderer('mod_assignment');
415             $output .= $renderer->assignment_files($this->context, $submission->id, 'response');
416         }
417         $output .= $OUTPUT->box_end();
419         if ($return) {
420             return $output;
421         }
422         echo $output;
423     }
426     /**
427      * Upload files
428      * upload_file function requires moodle form instance and file manager options
429      * @param object $mform
430      * @param array $options
431      */
432     function upload($mform = null, $filemanager_options = null) {
433         $action = required_param('action', PARAM_ALPHA);
434         switch ($action) {
435             case 'finalize':
436                 $this->finalize();
437                 break;
438             case 'finalizeclose':
439                 $this->finalizeclose();
440                 break;
441             case 'unfinalize':
442                 $this->unfinalize();
443                 break;
444             case 'uploadresponse':
445                 $this->upload_responsefile($mform, $filemanager_options);
446                 break;
447             case 'uploadfile':
448                 $this->upload_file($mform, $filemanager_options);
449             case 'savenotes':
450             case 'editnotes':
451                 $this->upload_notes();
452             default:
453                 print_error('unknowuploadaction', '', '', $action);
454         }
455     }
457     function upload_notes() {
458         global $CFG, $USER, $OUTPUT, $DB;
460         $action = required_param('action', PARAM_ALPHA);
462         $returnurl  = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
464         $mform = new mod_assignment_upload_notes_form();
466         $defaults = new stdClass();
467         $defaults->id = $this->cm->id;
469         if ($submission = $this->get_submission($USER->id)) {
470             $defaults->text = clean_text($submission->data1);
471         } else {
472             $defaults->text = '';
473         }
475         $mform->set_data($defaults);
477         if ($mform->is_cancelled()) {
478             $returnurl  = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
479             redirect($returnurl);
480         }
482         if (!$this->can_update_notes($submission)) {
483             $this->view_header(get_string('upload'));
484             echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
485             echo $OUTPUT->continue_button($returnurl);
486             $this->view_footer();
487             die;
488         }
490         if ($data = $mform->get_data() and $action == 'savenotes') {
491             $submission = $this->get_submission($USER->id, true); // get or create submission
492             $updated = new stdClass();
493             $updated->id           = $submission->id;
494             $updated->timemodified = time();
495             $updated->data1        = $data->text;
497             $DB->update_record('assignment_submissions', $updated);
498             add_to_log($this->course->id, 'assignment', 'upload', 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
499             redirect($returnurl);
500             $submission = $this->get_submission($USER->id);
501             $this->update_grade($submission);
502         }
504         /// show notes edit form
505         $this->view_header(get_string('notes', 'assignment'));
507         echo $OUTPUT->heading(get_string('notes', 'assignment'));
509         $mform->display();
511         $this->view_footer();
512         die;
513     }
515     function upload_responsefile($mform, $options) {
516         global $CFG, $USER, $OUTPUT, $PAGE;
518         $userid = required_param('userid', PARAM_INT);
519         $mode   = required_param('mode', PARAM_ALPHA);
520         $offset = required_param('offset', PARAM_INT);
522         $returnurl = new moodle_url("submissions.php", array('id'=>$this->cm->id,'userid'=>$userid,'mode'=>$mode,'offset'=>$offset)); //not xhtml, just url.
524         if ($formdata = $mform->get_data() and $this->can_manage_responsefiles()) {
525             $fs = get_file_storage();
526             $submission = $this->get_submission($userid, true, true);
527             if ($formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $this->context, 'mod_assignment', 'response', $submission->id)) {
528                 $returnurl = new moodle_url("/mod/assignment/submissions.php", array('id'=>$this->cm->id,'userid'=>$formdata->userid,'mode'=>$formdata->mode,'offset'=>$formdata->offset));
529                 redirect($returnurl->out(false));
530             }
531         }
532         $PAGE->set_title(get_string('upload'));
533         echo $OUTPUT->header();
534         echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
535         echo $OUTPUT->continue_button($returnurl->out(true));
536         echo $OUTPUT->footer();
537         die;
538     }
540     function upload_file($mform, $options) {
541         global $CFG, $USER, $DB, $OUTPUT;
543         $returnurl  = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
544         $submission = $this->get_submission($USER->id);
546         if (!$this->can_upload_file($submission)) {
547             $this->view_header(get_string('upload'));
548             echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
549             echo $OUTPUT->continue_button($returnurl);
550             $this->view_footer();
551             die;
552         }
554         if ($formdata = $mform->get_data()) {
555             $fs = get_file_storage();
556             $submission = $this->get_submission($USER->id, true); //create new submission if needed
557             $fs->delete_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id);
558             $formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $this->context, 'mod_assignment', 'submission', $submission->id);
559             $updates = new stdClass();
560             $updates->id = $submission->id;
561             $updates->timemodified = time();
562             $DB->update_record('assignment_submissions', $updates);
563             add_to_log($this->course->id, 'assignment', 'upload',
564                     'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
565             $this->update_grade($submission);
566             if (!$this->drafts_tracked()) {
567                 $this->email_teachers($submission);
568             }
570             // send files to event system
571             $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id);
572             // Let Moodle know that assessable files were  uploaded (eg for plagiarism detection)
573             $eventdata = new stdClass();
574             $eventdata->modulename   = 'assignment';
575             $eventdata->cmid         = $this->cm->id;
576             $eventdata->itemid       = $submission->id;
577             $eventdata->courseid     = $this->course->id;
578             $eventdata->userid       = $USER->id;
579             if ($files) {
580                 $eventdata->files        = $files;
581             }
582             events_trigger('assessable_file_uploaded', $eventdata);
583             $returnurl  = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
584             redirect($returnurl);
585         }
587         $this->view_header(get_string('upload'));
588         echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
589         echo $OUTPUT->continue_button($returnurl);
590         $this->view_footer();
591         die;
592     }
594     function send_file($filearea, $args) {
595         global $CFG, $DB, $USER;
596         require_once($CFG->libdir.'/filelib.php');
598         require_login($this->course, false, $this->cm);
600         if ($filearea === 'submission') {
601             $submissionid = (int)array_shift($args);
603             if (!$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'id'=>$submissionid))) {
604                 return false;
605             }
607             if ($USER->id != $submission->userid and !has_capability('mod/assignment:grade', $this->context)) {
608                 return false;
609             }
611             $relativepath = implode('/', $args);
612             $fullpath = "/{$this->context->id}/mod_assignment/submission/$submission->id/$relativepath";
614             $fs = get_file_storage();
615             if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
616                 return false;
617             }
618             send_stored_file($file, 0, 0, true); // download MUST be forced - security!
620         } else if ($filearea === 'response') {
621             $submissionid = (int)array_shift($args);
623             if (!$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'id'=>$submissionid))) {
624                 return false;
625             }
627             if ($USER->id != $submission->userid and !has_capability('mod/assignment:grade', $this->context)) {
628                 return false;
629             }
631             $relativepath = implode('/', $args);
632             $fullpath = "/{$this->context->id}/mod_assignment/response/$submission->id/$relativepath";
634             $fs = get_file_storage();
635             if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
636                 return false;
637             }
638             send_stored_file($file, 0, 0, true);
639         }
641         return false;
642     }
644     function finalize($forcemode=null) {
645         global $USER, $DB, $OUTPUT;
646         $userid = optional_param('userid', $USER->id, PARAM_INT);
647         $offset = optional_param('offset', 0, PARAM_INT);
648         $confirm    = optional_param('confirm', 0, PARAM_BOOL);
649         $returnurl  = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
650         $submission = $this->get_submission($userid);
652         if ($forcemode!=null) {
653             $returnurl  = new moodle_url('/mod/assignment/submissions.php',
654                 array('id'=>$this->cm->id,
655                     'userid'=>$userid,
656                     'mode'=>$forcemode,
657                     'offset'=>$offset
658                 ));
659         }
661         if (!$this->can_finalize($submission)) {
662             redirect($returnurl->out(false)); // probably already graded, redirect to assignment page, the reason should be obvious
663         }
665         if ($forcemode==null) {
666             if (!data_submitted() or !$confirm or !confirm_sesskey()) {
667                 $optionsno = array('id'=>$this->cm->id);
668                 $optionsyes = array ('id'=>$this->cm->id, 'confirm'=>1, 'action'=>'finalize', 'sesskey'=>sesskey());
669                 $this->view_header(get_string('submitformarking', 'assignment'));
670                 echo $OUTPUT->heading(get_string('submitformarking', 'assignment'));
671                 echo $OUTPUT->confirm(get_string('onceassignmentsent', 'assignment'), new moodle_url('upload.php', $optionsyes),new moodle_url( 'view.php', $optionsno));
672                 $this->view_footer();
673                 die;
674             }
675         }
676         $updated = new stdClass();
677         $updated->id           = $submission->id;
678         $updated->data2        = ASSIGNMENT_STATUS_SUBMITTED;
679         $updated->timemodified = time();
681         $DB->update_record('assignment_submissions', $updated);
682         add_to_log($this->course->id, 'assignment', 'upload', //TODO: add finalize action to log
683                 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
684         $submission = $this->get_submission($userid);
685         $this->update_grade($submission);
686         $this->email_teachers($submission);
688         // Trigger assessable_files_done event to show files are complete
689         $eventdata = new stdClass();
690         $eventdata->modulename   = 'assignment';
691         $eventdata->cmid         = $this->cm->id;
692         $eventdata->itemid       = $submission->id;
693         $eventdata->courseid     = $this->course->id;
694         $eventdata->userid       = $userid;
695         events_trigger('assessable_files_done', $eventdata);
697         if ($forcemode==null) {
698             redirect($returnurl->out(false));
699         }
700     }
702     function finalizeclose() {
703         global $DB;
705         $userid    = optional_param('userid', 0, PARAM_INT);
706         $mode      = required_param('mode', PARAM_ALPHA);
707         $offset    = required_param('offset', PARAM_INT);
708         $returnurl  = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'userid'=>$userid, 'mode'=>$mode, 'offset'=>$offset, 'forcerefresh'=>1));
710         // create but do not add student submission date
711         $submission = $this->get_submission($userid, true, true);
713         if (!data_submitted() or !$this->can_finalize($submission) or !confirm_sesskey()) {
714             redirect($returnurl); // probably closed already
715         }
717         $updated = new stdClass();
718         $updated->id    = $submission->id;
719         $updated->data2 = ASSIGNMENT_STATUS_CLOSED;
721         $DB->update_record('assignment_submissions', $updated);
722         add_to_log($this->course->id, 'assignment', 'upload', //TODO: add finalize action to log
723                 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
724         $submission = $this->get_submission($userid, false, true);
725         $this->update_grade($submission);
726         redirect($returnurl);
727     }
729     function unfinalize($forcemode=null) {
730         global $DB;
732         $userid = required_param('userid', PARAM_INT);
733         $mode   = required_param('mode', PARAM_ALPHA);
734         $offset = required_param('offset', PARAM_INT);
736         if ($forcemode!=null) {
737             $mode=$forcemode;
738         }
739         $returnurl = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'userid'=>$userid, 'mode'=>$mode, 'offset'=>$offset, 'forcerefresh'=>1) );
740         if (data_submitted()
741           and $submission = $this->get_submission($userid)
742           and $this->can_unfinalize($submission)
743           and confirm_sesskey()) {
745             $updated = new stdClass();
746             $updated->id = $submission->id;
747             $updated->data2 = '';
748             $DB->update_record('assignment_submissions', $updated);
749             //TODO: add unfinalize action to log
750             add_to_log($this->course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id.'&userid='.$userid.'&mode='.$mode.'&offset='.$offset, $this->assignment->id, $this->cm->id);
751             $submission = $this->get_submission($userid);
752             $this->update_grade($submission);
753         }
755         if ($forcemode==null) {
756             redirect($returnurl);
757         }
758     }
761     function delete() {
762         $action   = optional_param('action', '', PARAM_ALPHA);
764         switch ($action) {
765             case 'response':
766                 $this->delete_responsefile();
767                 break;
768             default:
769                 $this->delete_file();
770         }
771         die;
772     }
775     function delete_responsefile() {
776         global $CFG, $OUTPUT,$PAGE;
778         $file     = required_param('file', PARAM_FILE);
779         $userid   = required_param('userid', PARAM_INT);
780         $mode     = required_param('mode', PARAM_ALPHA);
781         $offset   = required_param('offset', PARAM_INT);
782         $confirm  = optional_param('confirm', 0, PARAM_BOOL);
784         $returnurl  = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'userid'=>$userid, 'mode'=>$mode, 'offset'=>$offset));
786         if (!$this->can_manage_responsefiles()) {
787            redirect($returnurl);
788         }
790         $urlreturn = 'submissions.php';
791         $optionsreturn = array('id'=>$this->cm->id, 'offset'=>$offset, 'mode'=>$mode, 'userid'=>$userid);
793         if (!data_submitted() or !$confirm or !confirm_sesskey()) {
794             $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'action'=>'response', 'mode'=>$mode, 'offset'=>$offset, 'sesskey'=>sesskey());
795             $PAGE->set_title(get_string('delete'));
796             echo $OUTPUT->header();
797             echo $OUTPUT->heading(get_string('delete'));
798             echo $OUTPUT->confirm(get_string('confirmdeletefile', 'assignment', $file), new moodle_url('delete.php', $optionsyes), new moodle_url($urlreturn, $optionsreturn));
799             echo $OUTPUT->footer();
800             die;
801         }
803         if ($submission = $this->get_submission($userid)) {
804             $fs = get_file_storage();
805             if ($file = $fs->get_file($this->context->id, 'mod_assignment', 'response', $submission->id, '/', $file)) {
806                 $file->delete();
807             }
808         }
809         redirect($returnurl);
810     }
813     function delete_file() {
814         global $CFG, $DB, $OUTPUT, $PAGE;
816         $file     = required_param('file', PARAM_FILE);
817         $userid   = required_param('userid', PARAM_INT);
818         $confirm  = optional_param('confirm', 0, PARAM_BOOL);
819         $mode     = optional_param('mode', '', PARAM_ALPHA);
820         $offset   = optional_param('offset', 0, PARAM_INT);
822         require_login($this->course->id, false, $this->cm);
824         if (empty($mode)) {
825             $urlreturn = 'view.php';
826             $optionsreturn = array('id'=>$this->cm->id);
827             $returnurl  = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
828         } else {
829             $urlreturn = 'submissions.php';
830             $optionsreturn = array('id'=>$this->cm->id, 'offset'=>$offset, 'mode'=>$mode, 'userid'=>$userid);
831             $returnurl  = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'offset'=>$offset, 'userid'=>$userid));
832         }
834         if (!$submission = $this->get_submission($userid) // incorrect submission
835           or !$this->can_delete_files($submission)) {     // can not delete
836             $this->view_header(get_string('delete'));
837             echo $OUTPUT->notification(get_string('cannotdeletefiles', 'assignment'));
838             echo $OUTPUT->continue_button($returnurl);
839             $this->view_footer();
840             die;
841         }
843         if (!data_submitted() or !$confirm or !confirm_sesskey()) {
844             $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'sesskey'=>sesskey(), 'mode'=>$mode, 'offset'=>$offset, 'sesskey'=>sesskey());
845             if (empty($mode)) {
846                 $this->view_header(get_string('delete'));
847             } else {
848                 $PAGE->set_title(get_string('delete'));
849                 echo $OUTPUT->header();
850             }
851             echo $OUTPUT->heading(get_string('delete'));
852             echo $OUTPUT->confirm(get_string('confirmdeletefile', 'assignment', $file), new moodle_url('delete.php', $optionsyes), new moodle_url($urlreturn, $optionsreturn));
853             if (empty($mode)) {
854                 $this->view_footer();
855             } else {
856                 echo $OUTPUT->footer();
857             }
858             die;
859         }
861         $fs = get_file_storage();
862         if ($file = $fs->get_file($this->context->id, 'mod_assignment', 'submission', $submission->id, '/', $file)) {
863             $file->delete();
864             $submission->timemodified = time();
865             $DB->update_record('assignment_submissions', $submission);
866             add_to_log($this->course->id, 'assignment', 'upload', //TODO: add delete action to log
867                     'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
868             $this->update_grade($submission);
869         }
870         redirect($returnurl);
871     }
874     function can_upload_file($submission) {
875         global $USER;
877         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
878           and $this->isopen()                                                 // assignment not closed yet
879           and (empty($submission) or ($submission->userid == $USER->id))        // his/her own submission
880           and !$this->is_finalized($submission)) {                            // no uploading after final submission
881             return true;
882         } else {
883             return false;
884         }
885     }
887     function can_manage_responsefiles() {
888         if (has_capability('mod/assignment:grade', $this->context)) {
889             return true;
890         } else {
891             return false;
892         }
893     }
895     function can_delete_files($submission) {
896         global $USER;
898         if (has_capability('mod/assignment:grade', $this->context)) {
899             return true;
900         }
902         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
903           and $this->isopen()                                      // assignment not closed yet
904           and $this->assignment->resubmit                          // deleting allowed
905           and $USER->id == $submission->userid                     // his/her own submission
906           and !$this->is_finalized($submission)) {                 // no deleting after final submission
907             return true;
908         } else {
909             return false;
910         }
911     }
913     function drafts_tracked() {
914         return !empty($this->assignment->var4);
915     }
917     /**
918      * Returns submission status
919      * @param object $submission - may be empty
920      * @return string submission state - empty, ASSIGNMENT_STATUS_SUBMITTED or ASSIGNMENT_STATUS_CLOSED
921      */
922     function is_finalized($submission) {
923         if (!$this->drafts_tracked()) {
924             return '';
926         } else if (empty($submission)) {
927             return '';
929         } else if ($submission->data2 == ASSIGNMENT_STATUS_SUBMITTED or $submission->data2 == ASSIGNMENT_STATUS_CLOSED) {
930             return $submission->data2;
932         } else {
933             return '';
934         }
935     }
937     function can_unfinalize($submission) {
938         if(is_bool($submission)) {
939             return false;
940         }
942         if (!$this->drafts_tracked()) {
943             return false;
944         }
946         if (has_capability('mod/assignment:grade', $this->context)
947           and $this->isopen()
948           and $this->is_finalized($submission)) {
949             return true;
950         } else {
951             return false;
952         }
953     }
955     function can_finalize($submission) {
956         global $USER;
958         if(is_bool($submission)) {
959             return false;
960         }
962         if (!$this->drafts_tracked()) {
963             return false;
964         }
966         if ($this->is_finalized($submission)) {
967             return false;
968         }
970         if (has_capability('mod/assignment:grade', $this->context)) {
971             return true;
973         } else if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
974           and $this->isopen()                                                 // assignment not closed yet
975           and !empty($submission)                                             // submission must exist
976           and $submission->userid == $USER->id                                // his/her own submission
977           and ($this->count_user_files($submission->id)
978             or ($this->notes_allowed() and !empty($submission->data1)))) {    // something must be submitted
980             return true;
981         } else {
982             return false;
983         }
984     }
986     function can_update_notes($submission) {
987         global $USER;
989         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
990           and $this->notes_allowed()                                          // notesd must be allowed
991           and $this->isopen()                                                 // assignment not closed yet
992           and (empty($submission) or $USER->id == $submission->userid)        // his/her own submission
993           and !$this->is_finalized($submission)) {                            // no updateingafter final submission
994             return true;
995         } else {
996             return false;
997         }
998     }
1000     function notes_allowed() {
1001         return (boolean)$this->assignment->var2;
1002     }
1004     function count_responsefiles($userid) {
1005         if ($submission = $this->get_submission($userid)) {
1006             $fs = get_file_storage();
1007             $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'response', $submission->id, "id", false);
1008             return count($files);
1009         } else {
1010             return 0;
1011         }
1012     }
1014     function setup_elements(&$mform) {
1015         global $CFG, $COURSE;
1017         $ynoptions = array( 0 => get_string('no'), 1 => get_string('yes'));
1019         $choices = get_max_upload_sizes($CFG->maxbytes, $COURSE->maxbytes);
1020         $choices[0] = get_string('courseuploadlimit') . ' ('.display_size($COURSE->maxbytes).')';
1021         $mform->addElement('select', 'maxbytes', get_string('maximumsize', 'assignment'), $choices);
1022         $mform->setDefault('maxbytes', $CFG->assignment_maxbytes);
1024         $mform->addElement('select', 'resubmit', get_string('allowdeleting', 'assignment'), $ynoptions);
1025         $mform->addHelpButton('resubmit', 'allowdeleting', 'assignment');
1026         $mform->setDefault('resubmit', 1);
1028         $options = array();
1029         for($i = 1; $i <= 20; $i++) {
1030             $options[$i] = $i;
1031         }
1032         $mform->addElement('select', 'var1', get_string('allowmaxfiles', 'assignment'), $options);
1033         $mform->addHelpButton('var1', 'allowmaxfiles', 'assignment');
1034         $mform->setDefault('var1', 3);
1036         $mform->addElement('select', 'var2', get_string('allownotes', 'assignment'), $ynoptions);
1037         $mform->addHelpButton('var2', 'allownotes', 'assignment');
1038         $mform->setDefault('var2', 0);
1040         $mform->addElement('select', 'var3', get_string('hideintro', 'assignment'), $ynoptions);
1041         $mform->addHelpButton('var3', 'hideintro', 'assignment');
1042         $mform->setDefault('var3', 0);
1044         $mform->addElement('select', 'emailteachers', get_string('emailteachers', 'assignment'), $ynoptions);
1045         $mform->addHelpButton('emailteachers', 'emailteachers', 'assignment');
1046         $mform->setDefault('emailteachers', 0);
1048         $mform->addElement('select', 'var4', get_string('trackdrafts', 'assignment'), $ynoptions);
1049         $mform->addHelpButton('var4', 'trackdrafts', 'assignment');
1050         $mform->setDefault('var4', 1);
1052         $course_context = get_context_instance(CONTEXT_COURSE, $COURSE->id);
1053         plagiarism_get_form_elements_module($mform, $course_context);
1054     }
1056     function portfolio_exportable() {
1057         return true;
1058     }
1060     function extend_settings_navigation($node) {
1061         global $CFG, $USER, $OUTPUT;
1063         // get users submission if there is one
1064         $submission = $this->get_submission();
1065         if (is_enrolled($this->context, $USER, 'mod/assignment:submit')) {
1066             $editable = $this->isopen() && (!$submission || $this->assignment->resubmit || !$submission->timemarked);
1067         } else {
1068             $editable = false;
1069         }
1071         // If the user has submitted something add some related links and data
1072         if (isset($submission->data2) AND $submission->data2 == 'submitted') {
1073             // Add a view link to the settings nav
1074             $link = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
1075             $node->add(get_string('viewmysubmission', 'assignment'), $link, navigation_node::TYPE_SETTING);
1076             if (!empty($submission->timemodified)) {
1077                 $submittednode = $node->add(get_string('submitted', 'assignment') . ' ' . userdate($submission->timemodified));
1078                 $submittednode->text = preg_replace('#([^,])\s#', '$1&nbsp;', $submittednode->text);
1079                 $submittednode->add_class('note');
1080                 if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
1081                     $submittednode->add_class('early');
1082                 } else {
1083                     $submittednode->add_class('late');
1084                 }
1085             }
1086         }
1088         // Check if the user has uploaded any files, if so we can add some more stuff to the settings nav
1089         if ($submission && is_enrolled($this->context, $USER, 'mod/assignment:submit') && $this->count_user_files($submission->id)) {
1090             $fs = get_file_storage();
1091             if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
1092                 if (!$this->drafts_tracked() or !$this->isopen() or $this->is_finalized($submission)) {
1093                     $filenode = $node->add(get_string('submission', 'assignment'));
1094                 } else {
1095                     $filenode = $node->add(get_string('submissiondraft', 'assignment'));
1096                 }
1097                 foreach ($files as $file) {
1098                     $filename = $file->get_filename();
1099                     $mimetype = $file->get_mimetype();
1100                     $link = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
1101                     $filenode->add($filename, $link, navigation_node::TYPE_SETTING, null, null, new pix_icon(file_mimetype_icon($mimetype),''));
1102                 }
1103             }
1104         }
1106         // Show a notes link if they are enabled
1107         if ($this->notes_allowed()) {
1108             $link = new moodle_url('/mod/assignment/upload.php', array('id'=>$this->cm->id, 'action'=>'editnotes', 'sesskey'=>sesskey()));
1109             $node->add(get_string('notes', 'assignment'), $link);
1110         }
1111     }
1113     /**
1114      * creates a zip of all assignment submissions and sends a zip to the browser
1115      */
1116     public function download_submissions() {
1117         global $CFG,$DB;
1118         require_once($CFG->libdir.'/filelib.php');
1119         $submissions = $this->get_submissions('','');
1120         if (empty($submissions)) {
1121             print_error('errornosubmissions', 'assignment');
1122         }
1123         $filesforzipping = array();
1124         $fs = get_file_storage();
1126         $groupmode = groups_get_activity_groupmode($this->cm);
1127         $groupid = 0;   // All users
1128         $groupname = '';
1129         if ($groupmode) {
1130             $groupid = groups_get_activity_group($this->cm, true);
1131             $groupname = groups_get_group_name($groupid).'-';
1132         }
1133         $filename = str_replace(' ', '_', clean_filename($this->course->shortname.'-'.$this->assignment->name.'-'.$groupname.$this->assignment->id.".zip")); //name of new zip file.
1134         foreach ($submissions as $submission) {
1135             $a_userid = $submission->userid; //get userid
1136             if ((groups_is_member($groupid,$a_userid)or !$groupmode or !$groupid)) {
1137                 $a_assignid = $submission->assignment; //get name of this assignment for use in the file names.
1138                 $a_user = $DB->get_record("user", array("id"=>$a_userid),'id,username,firstname,lastname'); //get user firstname/lastname
1140                 $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
1141                 foreach ($files as $file) {
1142                     //get files new name.
1143                     $fileext = strstr($file->get_filename(), '.');
1144                     $fileoriginal = str_replace($fileext, '', $file->get_filename());
1145                     $fileforzipname =  clean_filename(fullname($a_user) . "_" . $fileoriginal."_".$a_userid.$fileext);
1146                     //save file name to array for zipping.
1147                     $filesforzipping[$fileforzipname] = $file;
1148                 }
1149             }
1150         } // end of foreach loop
1151         if ($zipfile = assignment_pack_files($filesforzipping)) {
1152             send_temp_file($zipfile, $filename); //send file and delete after sending.
1153         }
1154     }
1157 class mod_assignment_upload_notes_form extends moodleform {
1159     function get_data() {
1160         $data = parent::get_data();
1161         if ($data) {
1162             $data->format = $data->text['format'];
1163             $data->text = $data->text['text'];
1164         }
1165         return $data;
1166     }
1168     function set_data($data) {
1169         if (!isset($data->format)) {
1170             $data->format = FORMAT_HTML;
1171         }
1172         if (isset($data->text)) {
1173             $data->text = array('text'=>$data->text, 'format'=>$data->format);
1174         }
1175         parent::set_data($data);
1176     }
1178     function definition() {
1179         $mform = $this->_form;
1181         // visible elements
1182         $mform->addElement('editor', 'text', get_string('notes', 'assignment'), null, null);
1183         $mform->setType('text', PARAM_RAW); // to be cleaned before display
1185         // hidden params
1186         $mform->addElement('hidden', 'id', 0);
1187         $mform->setType('id', PARAM_INT);
1188         $mform->addElement('hidden', 'action', 'savenotes');
1189         $mform->setType('action', PARAM_ALPHA);
1191         // buttons
1192         $this->add_action_buttons();
1193     }
1196 class mod_assignment_upload_response_form extends moodleform {
1197     function definition() {
1198         $mform = $this->_form;
1199         $instance = $this->_customdata;
1201         // visible elements
1202         $mform->addElement('filemanager', 'files_filemanager', get_string('uploadafile'), null, $instance->options);
1204         // hidden params
1205         $mform->addElement('hidden', 'id', $instance->cm->id);
1206         $mform->setType('id', PARAM_INT);
1207         $mform->addElement('hidden', 'contextid', $instance->contextid);
1208         $mform->setType('contextid', PARAM_INT);
1209         $mform->addElement('hidden', 'action', 'uploadresponse');
1210         $mform->setType('action', PARAM_ALPHA);
1211         $mform->addElement('hidden', 'mode', $instance->mode);
1212         $mform->setType('mode', PARAM_ALPHA);
1213         $mform->addElement('hidden', 'offset', $instance->offset);
1214         $mform->setType('offset', PARAM_INT);
1215         $mform->addElement('hidden', 'forcerefresh' , $instance->forcerefresh);
1216         $mform->setType('forcerefresh', PARAM_INT);
1217         $mform->addElement('hidden', 'userid', $instance->userid);
1218         $mform->setType('userid', PARAM_INT);
1220         // buttons
1221         $this->add_action_buttons(false, get_string('uploadthisfile'));
1222     }