PageRenderTime 68ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/mod/assignment/type/upload/assignment.class.php

https://bitbucket.org/andrewdavidson/sl-clone
PHP | 1271 lines | 947 code | 222 blank | 102 comment | 179 complexity | ac74e834710eca52a4daadc8ec2dd97a MD5 | raw file
Possible License(s): AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, Apache-2.0, GPL-3.0, BSD-3-Clause, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Assignment upload type implementation
  18. *
  19. * @package mod-assignment
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. require_once(dirname(__FILE__).'/upload_form.php');
  23. require_once($CFG->libdir . '/portfoliolib.php');
  24. require_once($CFG->dirroot . '/mod/assignment/lib.php');
  25. define('ASSIGNMENT_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
  26. define('ASSIGNMENT_STATUS_CLOSED', 'closed'); // teacher prevents more submissions
  27. /**
  28. * Extend the base assignment class for assignments where you upload a single file
  29. *
  30. */
  31. class assignment_upload extends assignment_base {
  32. function assignment_upload($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
  33. parent::assignment_base($cmid, $assignment, $cm, $course);
  34. $this->type = 'upload';
  35. }
  36. function view() {
  37. global $USER, $OUTPUT;
  38. require_capability('mod/assignment:view', $this->context);
  39. $cansubmit = has_capability('mod/assignment:submit', $this->context);
  40. add_to_log($this->course->id, 'assignment', 'view', "view.php?id={$this->cm->id}", $this->assignment->id, $this->cm->id);
  41. $this->view_header();
  42. if ($this->assignment->timeavailable > time()
  43. and !has_capability('mod/assignment:grade', $this->context) // grading user can see it anytime
  44. and $this->assignment->var3) { // force hiding before available date
  45. echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
  46. print_string('notavailableyet', 'assignment');
  47. echo $OUTPUT->box_end();
  48. } else {
  49. $this->view_intro();
  50. }
  51. $this->view_dates();
  52. if (is_enrolled($this->context, $USER)) {
  53. if ($submission = $this->get_submission($USER->id)) {
  54. $filecount = $this->count_user_files($submission->id);
  55. } else {
  56. $filecount = 0;
  57. }
  58. if ($cansubmit or !empty($filecount)) { //if a user has submitted files using a previous role we should still show the files
  59. $this->view_feedback();
  60. if (!$this->drafts_tracked() or !$this->isopen() or $this->is_finalized($submission)) {
  61. echo $OUTPUT->heading(get_string('submission', 'assignment'), 3);
  62. } else {
  63. echo $OUTPUT->heading(get_string('submissiondraft', 'assignment'), 3);
  64. }
  65. if ($filecount and $submission) {
  66. echo $OUTPUT->box($this->print_user_files($USER->id, true), 'generalbox boxaligncenter', 'userfiles');
  67. } else {
  68. if (!$this->isopen() or $this->is_finalized($submission)) {
  69. echo $OUTPUT->box(get_string('nofiles', 'assignment'), 'generalbox boxaligncenter nofiles', 'userfiles');
  70. } else {
  71. echo $OUTPUT->box(get_string('nofilesyet', 'assignment'), 'generalbox boxaligncenter nofiles', 'userfiles');
  72. }
  73. }
  74. $this->view_upload_form();
  75. if ($this->notes_allowed()) {
  76. echo $OUTPUT->heading(get_string('notes', 'assignment'), 3);
  77. $this->view_notes();
  78. }
  79. $this->view_final_submission();
  80. }
  81. }
  82. $this->view_footer();
  83. }
  84. function view_feedback($submission=NULL) {
  85. global $USER, $CFG, $DB, $OUTPUT, $PAGE;
  86. require_once($CFG->libdir.'/gradelib.php');
  87. require_once("$CFG->dirroot/grade/grading/lib.php");
  88. if (!$submission) { /// Get submission for this assignment
  89. $userid = $USER->id;
  90. $submission = $this->get_submission($userid);
  91. } else {
  92. $userid = $submission->userid;
  93. }
  94. // Check the user can submit
  95. $canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
  96. // If not then check if the user still has the view cap and has a previous submission
  97. $canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
  98. // Or if user can grade (is a teacher or admin)
  99. $canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
  100. if (!$canviewfeedback) {
  101. // can not view or submit assignments -> no feedback
  102. return;
  103. }
  104. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
  105. $item = $grading_info->items[0];
  106. $grade = $item->grades[$userid];
  107. if ($grade->hidden or $grade->grade === false) { // hidden or error
  108. return;
  109. }
  110. if ($grade->grade === null and empty($grade->str_feedback)) { // No grade to show yet
  111. if ($this->count_responsefiles($userid)) { // but possibly response files are present
  112. echo $OUTPUT->heading(get_string('responsefiles', 'assignment'), 3);
  113. $responsefiles = $this->print_responsefiles($userid, true);
  114. echo $OUTPUT->box($responsefiles, 'generalbox boxaligncenter');
  115. }
  116. return;
  117. }
  118. $graded_date = $grade->dategraded;
  119. $graded_by = $grade->usermodified;
  120. /// We need the teacher info
  121. if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
  122. print_error('cannotfindteacher');
  123. }
  124. /// Print the feedback
  125. echo $OUTPUT->heading(get_string('submissionfeedback', 'assignment'), 3);
  126. echo '<table cellspacing="0" class="feedback">';
  127. echo '<tr>';
  128. echo '<td class="left picture">';
  129. echo $OUTPUT->user_picture($teacher);
  130. echo '</td>';
  131. echo '<td class="topic">';
  132. echo '<div class="from">';
  133. echo '<div class="fullname">'.fullname($teacher).'</div>';
  134. echo '<div class="time">'.userdate($graded_date).'</div>';
  135. echo '</div>';
  136. echo '</td>';
  137. echo '</tr>';
  138. echo '<tr>';
  139. echo '<td class="left side">&nbsp;</td>';
  140. echo '<td class="content">';
  141. $gradestr = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
  142. if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
  143. $controller->set_grade_range(make_grades_menu($this->assignment->grade));
  144. echo $controller->render_grade($PAGE, $submission->id, $item, $gradestr, has_capability('mod/assignment:grade', $this->context));
  145. } else {
  146. echo $gradestr;
  147. }
  148. echo '<div class="clearer"></div>';
  149. echo '<div class="comment">';
  150. echo $grade->str_feedback;
  151. echo '</div>';
  152. echo '</tr>';
  153. echo '<tr>';
  154. echo '<td class="left side">&nbsp;</td>';
  155. echo '<td class="content">';
  156. echo $this->print_responsefiles($userid, true);
  157. echo '</tr>';
  158. echo '</table>';
  159. }
  160. function view_upload_form() {
  161. global $CFG, $USER, $OUTPUT;
  162. $submission = $this->get_submission($USER->id);
  163. if ($this->is_finalized($submission)) {
  164. // no uploading
  165. return;
  166. }
  167. if ($this->can_upload_file($submission)) {
  168. $fs = get_file_storage();
  169. // edit files in another page
  170. if ($submission) {
  171. if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
  172. $str = get_string('editthesefiles', 'assignment');
  173. } else {
  174. $str = get_string('uploadfiles', 'assignment');
  175. }
  176. } else {
  177. $str = get_string('uploadfiles', 'assignment');
  178. }
  179. echo $OUTPUT->single_button(new moodle_url('/mod/assignment/type/upload/upload.php', array('contextid'=>$this->context->id, 'userid'=>$USER->id)), $str, 'get');
  180. }
  181. }
  182. function view_notes() {
  183. global $USER, $OUTPUT;
  184. if ($submission = $this->get_submission($USER->id)
  185. and !empty($submission->data1)) {
  186. echo $OUTPUT->box(format_text($submission->data1, FORMAT_HTML, array('overflowdiv'=>true)), 'generalbox boxaligncenter boxwidthwide');
  187. } else {
  188. echo $OUTPUT->box(get_string('notesempty', 'assignment'), 'generalbox boxaligncenter');
  189. }
  190. if ($this->can_update_notes($submission)) {
  191. $options = array ('id'=>$this->cm->id, 'action'=>'editnotes');
  192. echo '<div style="text-align:center">';
  193. echo $OUTPUT->single_button(new moodle_url('upload.php', $options), get_string('edit'));
  194. echo '</div>';
  195. }
  196. }
  197. function view_final_submission() {
  198. global $CFG, $USER, $OUTPUT;
  199. $submission = $this->get_submission($USER->id);
  200. if ($this->isopen() and $this->can_finalize($submission)) {
  201. //print final submit button
  202. echo $OUTPUT->heading(get_string('submitformarking','assignment'), 3);
  203. echo '<div style="text-align:center">';
  204. echo '<form method="post" action="upload.php">';
  205. echo '<fieldset class="invisiblefieldset">';
  206. echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
  207. echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
  208. echo '<input type="hidden" name="action" value="finalize" />';
  209. echo '<input type="submit" name="formarking" value="'.get_string('sendformarking', 'assignment').'" />';
  210. echo '</fieldset>';
  211. echo '</form>';
  212. echo '</div>';
  213. } else if (!$this->isopen()) {
  214. echo $OUTPUT->heading(get_string('nomoresubmissions','assignment'), 3);
  215. } else if ($this->drafts_tracked() and $state = $this->is_finalized($submission)) {
  216. if ($state == ASSIGNMENT_STATUS_SUBMITTED) {
  217. echo $OUTPUT->heading(get_string('submitedformarking','assignment'), 3);
  218. } else {
  219. echo $OUTPUT->heading(get_string('nomoresubmissions','assignment'), 3);
  220. }
  221. } else {
  222. //no submission yet
  223. }
  224. }
  225. /**
  226. * Return true if var3 == hide description till available day
  227. *
  228. *@return boolean
  229. */
  230. function description_is_hidden() {
  231. return ($this->assignment->var3 && (time() <= $this->assignment->timeavailable));
  232. }
  233. function print_student_answer($userid, $return=false){
  234. global $CFG, $OUTPUT, $PAGE;
  235. $submission = $this->get_submission($userid);
  236. $output = '';
  237. if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission)) {
  238. $output .= '<strong>'.get_string('draft', 'assignment').':</strong> ';
  239. }
  240. if ($this->notes_allowed() and !empty($submission->data1)) {
  241. $link = new moodle_url("/mod/assignment/type/upload/notes.php", array('id'=>$this->cm->id, 'userid'=>$userid));
  242. $action = new popup_action('click', $link, 'notes', array('height' => 500, 'width' => 780));
  243. $output .= $OUTPUT->action_link($link, get_string('notes', 'assignment'), $action, array('title'=>get_string('notes', 'assignment')));
  244. $output .= '&nbsp;';
  245. }
  246. $renderer = $PAGE->get_renderer('mod_assignment');
  247. $output = $OUTPUT->box_start('files').$output;
  248. $output .= $renderer->assignment_files($this->context, $submission->id);
  249. $output .= $OUTPUT->box_end();
  250. return $output;
  251. }
  252. /**
  253. * Produces a list of links to the files uploaded by a user
  254. *
  255. * @param $userid int optional id of the user. If 0 then $USER->id is used.
  256. * @param $return boolean optional defaults to false. If true the list is returned rather than printed
  257. * @return string optional
  258. */
  259. function print_user_files($userid=0, $return=false) {
  260. global $CFG, $USER, $OUTPUT, $PAGE;
  261. $mode = optional_param('mode', '', PARAM_ALPHA);
  262. $offset = optional_param('offset', 0, PARAM_INT);
  263. if (!$userid) {
  264. if (!isloggedin()) {
  265. return '';
  266. }
  267. $userid = $USER->id;
  268. }
  269. $output = $OUTPUT->box_start('files');
  270. $submission = $this->get_submission($userid);
  271. // only during grading
  272. if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission) and !empty($mode)) {
  273. $output .= '<strong>'.get_string('draft', 'assignment').':</strong><br />';
  274. }
  275. if ($this->notes_allowed() and !empty($submission->data1) and !empty($mode)) { // only during grading
  276. $npurl = $CFG->wwwroot."/mod/assignment/type/upload/notes.php?id={$this->cm->id}&amp;userid=$userid&amp;offset=$offset&amp;mode=single";
  277. $output .= '<a href="'.$npurl.'">'.get_string('notes', 'assignment').'</a><br />';
  278. }
  279. 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
  280. if ($this->can_unfinalize($submission)) {
  281. //$options = array ('id'=>$this->cm->id, 'userid'=>$userid, 'action'=>'unfinalize', 'mode'=>$mode, 'offset'=>$offset);
  282. $output .= '<br /><input type="submit" name="unfinalize" value="'.get_string('unfinalize', 'assignment').'" />';
  283. $output .= $OUTPUT->help_icon('unfinalize', 'assignment');
  284. } else if ($this->can_finalize($submission)) {
  285. //$options = array ('id'=>$this->cm->id, 'userid'=>$userid, 'action'=>'finalizeclose', 'mode'=>$mode, 'offset'=>$offset);
  286. $output .= '<br /><input type="submit" name="finalize" value="'.get_string('finalize', 'assignment').'" />';
  287. }
  288. }
  289. if ($submission) {
  290. $renderer = $PAGE->get_renderer('mod_assignment');
  291. $output .= $renderer->assignment_files($this->context, $submission->id);
  292. }
  293. $output .= $OUTPUT->box_end();
  294. if ($return) {
  295. return $output;
  296. }
  297. echo $output;
  298. }
  299. function submissions($mode) {
  300. // redirects out of form to process (un)finalizing.
  301. $unfinalize = optional_param('unfinalize', FALSE, PARAM_TEXT);
  302. $finalize = optional_param('finalize', FALSE, PARAM_TEXT);
  303. if ($unfinalize) {
  304. $this->unfinalize('single');
  305. } else if ($finalize) {
  306. $this->finalize('single');
  307. }
  308. if ($unfinalize || $finalize) {
  309. $mode = 'singlenosave';
  310. }
  311. parent::submissions($mode);
  312. }
  313. function process_feedback($formdata=null) {
  314. if (!$feedback = data_submitted() or !confirm_sesskey()) { // No incoming data?
  315. return false;
  316. }
  317. $userid = required_param('userid', PARAM_INT);
  318. $offset = required_param('offset', PARAM_INT);
  319. $mform = $this->display_submission($offset, $userid, false);
  320. parent::process_feedback($mform);
  321. }
  322. /**
  323. * Counts all complete (real) assignment submissions by enrolled students. This overrides assignment_base::count_real_submissions().
  324. * This is necessary for tracked advanced file uploads where we need to check that the data2 field is equal to ASSIGNMENT_STATUS_SUBMITTED
  325. * to determine if a submission is complete.
  326. *
  327. * @param int $groupid (optional) If nonzero then count is restricted to this group
  328. * @return int The number of submissions
  329. */
  330. function count_real_submissions($groupid=0) {
  331. global $DB;
  332. // Grab the context assocated with our course module
  333. $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
  334. // Get ids of users enrolled in the given course.
  335. list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
  336. $params['assignmentid'] = $this->cm->instance;
  337. $query = '';
  338. if ($this->drafts_tracked() and $this->isopen()) {
  339. $query = ' AND ' . $DB->sql_compare_text('s.data2') . " = '" . ASSIGNMENT_STATUS_SUBMITTED . "'";
  340. } else {
  341. // Count on submissions with files actually uploaded
  342. $query = " AND s.numfiles > 0";
  343. }
  344. return $DB->count_records_sql("SELECT COUNT('x')
  345. FROM {assignment_submissions} s
  346. LEFT JOIN {assignment} a ON a.id = s.assignment
  347. INNER JOIN ($enroledsql) u ON u.id = s.userid
  348. WHERE s.assignment = :assignmentid" .
  349. $query, $params);
  350. }
  351. function print_responsefiles($userid, $return=false) {
  352. global $CFG, $USER, $OUTPUT, $PAGE;
  353. $mode = optional_param('mode', '', PARAM_ALPHA);
  354. $offset = optional_param('offset', 0, PARAM_INT);
  355. $output = $OUTPUT->box_start('responsefiles');
  356. $candelete = $this->can_manage_responsefiles();
  357. $strdelete = get_string('delete');
  358. $fs = get_file_storage();
  359. $browser = get_file_browser();
  360. if ($submission = $this->get_submission($userid)) {
  361. $renderer = $PAGE->get_renderer('mod_assignment');
  362. $output .= $renderer->assignment_files($this->context, $submission->id, 'response');
  363. }
  364. $output .= $OUTPUT->box_end();
  365. if ($return) {
  366. return $output;
  367. }
  368. echo $output;
  369. }
  370. /**
  371. * Upload files
  372. * upload_file function requires moodle form instance and file manager options
  373. * @param object $mform
  374. * @param array $options
  375. */
  376. function upload($mform = null, $filemanager_options = null) {
  377. $action = required_param('action', PARAM_ALPHA);
  378. switch ($action) {
  379. case 'finalize':
  380. $this->finalize();
  381. break;
  382. case 'finalizeclose':
  383. $this->finalizeclose();
  384. break;
  385. case 'unfinalize':
  386. $this->unfinalize();
  387. break;
  388. case 'uploadresponse':
  389. $this->upload_responsefile($mform, $filemanager_options);
  390. break;
  391. case 'uploadfile':
  392. $this->upload_file($mform, $filemanager_options);
  393. case 'savenotes':
  394. case 'editnotes':
  395. $this->upload_notes();
  396. default:
  397. print_error('unknowuploadaction', '', '', $action);
  398. }
  399. }
  400. function upload_notes() {
  401. global $CFG, $USER, $OUTPUT, $DB;
  402. $action = required_param('action', PARAM_ALPHA);
  403. $returnurl = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  404. $mform = new mod_assignment_upload_notes_form();
  405. $defaults = new stdClass();
  406. $defaults->id = $this->cm->id;
  407. if ($submission = $this->get_submission($USER->id)) {
  408. $defaults->text = clean_text($submission->data1);
  409. } else {
  410. $defaults->text = '';
  411. }
  412. $mform->set_data($defaults);
  413. if ($mform->is_cancelled()) {
  414. $returnurl = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  415. redirect($returnurl);
  416. }
  417. if (!$this->can_update_notes($submission)) {
  418. $this->view_header(get_string('upload'));
  419. echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
  420. echo $OUTPUT->continue_button($returnurl);
  421. $this->view_footer();
  422. die;
  423. }
  424. if ($data = $mform->get_data() and $action == 'savenotes') {
  425. $submission = $this->get_submission($USER->id, true); // get or create submission
  426. $updated = new stdClass();
  427. $updated->id = $submission->id;
  428. $updated->timemodified = time();
  429. $updated->data1 = $data->text;
  430. $DB->update_record('assignment_submissions', $updated);
  431. add_to_log($this->course->id, 'assignment', 'upload', 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
  432. redirect($returnurl);
  433. $submission = $this->get_submission($USER->id);
  434. $this->update_grade($submission);
  435. }
  436. /// show notes edit form
  437. $this->view_header(get_string('notes', 'assignment'));
  438. echo $OUTPUT->heading(get_string('notes', 'assignment'));
  439. $mform->display();
  440. $this->view_footer();
  441. die;
  442. }
  443. function upload_responsefile($mform, $options) {
  444. global $CFG, $USER, $OUTPUT, $PAGE;
  445. $userid = required_param('userid', PARAM_INT);
  446. $mode = required_param('mode', PARAM_ALPHA);
  447. $offset = required_param('offset', PARAM_INT);
  448. $returnurl = new moodle_url("submissions.php", array('id'=>$this->cm->id,'userid'=>$userid,'mode'=>$mode,'offset'=>$offset)); //not xhtml, just url.
  449. if ($formdata = $mform->get_data() and $this->can_manage_responsefiles()) {
  450. $fs = get_file_storage();
  451. $submission = $this->get_submission($userid, true, true);
  452. if ($formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $this->context, 'mod_assignment', 'response', $submission->id)) {
  453. $returnurl = new moodle_url("/mod/assignment/submissions.php", array('id'=>$this->cm->id,'userid'=>$formdata->userid,'mode'=>$formdata->mode,'offset'=>$formdata->offset));
  454. redirect($returnurl->out(false));
  455. }
  456. }
  457. $PAGE->set_title(get_string('upload'));
  458. echo $OUTPUT->header();
  459. echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
  460. echo $OUTPUT->continue_button($returnurl->out(true));
  461. echo $OUTPUT->footer();
  462. die;
  463. }
  464. function upload_file($mform, $options) {
  465. global $CFG, $USER, $DB, $OUTPUT;
  466. $returnurl = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  467. $submission = $this->get_submission($USER->id);
  468. if (!$this->can_upload_file($submission)) {
  469. $this->view_header(get_string('upload'));
  470. echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
  471. echo $OUTPUT->continue_button($returnurl);
  472. $this->view_footer();
  473. die;
  474. }
  475. if ($formdata = $mform->get_data()) {
  476. $fs = get_file_storage();
  477. $submission = $this->get_submission($USER->id, true); //create new submission if needed
  478. $fs->delete_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id);
  479. $formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $this->context, 'mod_assignment', 'submission', $submission->id);
  480. $updates = new stdClass();
  481. $updates->id = $submission->id;
  482. $updates->numfiles = count($fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, 'sortorder', false));
  483. $updates->timemodified = time();
  484. $DB->update_record('assignment_submissions', $updates);
  485. add_to_log($this->course->id, 'assignment', 'upload',
  486. 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
  487. $this->update_grade($submission);
  488. if (!$this->drafts_tracked()) {
  489. $this->email_teachers($submission);
  490. }
  491. // send files to event system
  492. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id);
  493. // Let Moodle know that assessable files were uploaded (eg for plagiarism detection)
  494. $eventdata = new stdClass();
  495. $eventdata->modulename = 'assignment';
  496. $eventdata->cmid = $this->cm->id;
  497. $eventdata->itemid = $submission->id;
  498. $eventdata->courseid = $this->course->id;
  499. $eventdata->userid = $USER->id;
  500. if ($files) {
  501. $eventdata->files = $files; // This is depreceated - please use pathnamehashes instead!
  502. }
  503. $eventdata->pathnamehashes = array_keys($files);
  504. events_trigger('assessable_file_uploaded', $eventdata);
  505. $returnurl = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  506. redirect($returnurl);
  507. }
  508. $this->view_header(get_string('upload'));
  509. echo $OUTPUT->notification(get_string('uploaderror', 'assignment'));
  510. echo $OUTPUT->continue_button($returnurl);
  511. $this->view_footer();
  512. die;
  513. }
  514. function send_file($filearea, $args, $forcedownload, array $options=array()) {
  515. global $CFG, $DB, $USER;
  516. require_once($CFG->libdir.'/filelib.php');
  517. require_login($this->course, false, $this->cm);
  518. if ($filearea === 'submission') {
  519. $submissionid = (int)array_shift($args);
  520. if (!$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'id'=>$submissionid))) {
  521. return false;
  522. }
  523. if ($USER->id != $submission->userid and !has_capability('mod/assignment:grade', $this->context)) {
  524. return false;
  525. }
  526. $relativepath = implode('/', $args);
  527. $fullpath = "/{$this->context->id}/mod_assignment/submission/$submission->id/$relativepath";
  528. $fs = get_file_storage();
  529. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  530. return false;
  531. }
  532. send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
  533. } else if ($filearea === 'response') {
  534. $submissionid = (int)array_shift($args);
  535. if (!$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'id'=>$submissionid))) {
  536. return false;
  537. }
  538. if ($USER->id != $submission->userid and !has_capability('mod/assignment:grade', $this->context)) {
  539. return false;
  540. }
  541. $relativepath = implode('/', $args);
  542. $fullpath = "/{$this->context->id}/mod_assignment/response/$submission->id/$relativepath";
  543. $fs = get_file_storage();
  544. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  545. return false;
  546. }
  547. send_stored_file($file, 0, 0, true, $options);
  548. }
  549. return false;
  550. }
  551. function finalize($forcemode=null) {
  552. global $USER, $DB, $OUTPUT;
  553. $userid = optional_param('userid', $USER->id, PARAM_INT);
  554. $offset = optional_param('offset', 0, PARAM_INT);
  555. $confirm = optional_param('confirm', 0, PARAM_BOOL);
  556. $returnurl = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  557. $submission = $this->get_submission($userid);
  558. if ($forcemode!=null) {
  559. $returnurl = new moodle_url('/mod/assignment/submissions.php',
  560. array('id'=>$this->cm->id,
  561. 'userid'=>$userid,
  562. 'mode'=>$forcemode,
  563. 'offset'=>$offset
  564. ));
  565. }
  566. if (!$this->can_finalize($submission)) {
  567. redirect($returnurl->out(false)); // probably already graded, redirect to assignment page, the reason should be obvious
  568. }
  569. if ($forcemode==null) {
  570. if (!data_submitted() or !$confirm or !confirm_sesskey()) {
  571. $optionsno = array('id'=>$this->cm->id);
  572. $optionsyes = array ('id'=>$this->cm->id, 'confirm'=>1, 'action'=>'finalize', 'sesskey'=>sesskey());
  573. $this->view_header(get_string('submitformarking', 'assignment'));
  574. echo $OUTPUT->heading(get_string('submitformarking', 'assignment'));
  575. echo $OUTPUT->confirm(get_string('onceassignmentsent', 'assignment'), new moodle_url('upload.php', $optionsyes),new moodle_url( 'view.php', $optionsno));
  576. $this->view_footer();
  577. die;
  578. }
  579. }
  580. $updated = new stdClass();
  581. $updated->id = $submission->id;
  582. $updated->data2 = ASSIGNMENT_STATUS_SUBMITTED;
  583. $updated->timemodified = time();
  584. $DB->update_record('assignment_submissions', $updated);
  585. add_to_log($this->course->id, 'assignment', 'upload', //TODO: add finalize action to log
  586. 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
  587. $submission = $this->get_submission($userid);
  588. $this->update_grade($submission);
  589. $this->email_teachers($submission);
  590. // Trigger assessable_files_done event to show files are complete
  591. $eventdata = new stdClass();
  592. $eventdata->modulename = 'assignment';
  593. $eventdata->cmid = $this->cm->id;
  594. $eventdata->itemid = $submission->id;
  595. $eventdata->courseid = $this->course->id;
  596. $eventdata->userid = $userid;
  597. events_trigger('assessable_files_done', $eventdata);
  598. if ($forcemode==null) {
  599. redirect($returnurl->out(false));
  600. }
  601. }
  602. function finalizeclose() {
  603. global $DB;
  604. $userid = optional_param('userid', 0, PARAM_INT);
  605. $mode = required_param('mode', PARAM_ALPHA);
  606. $offset = required_param('offset', PARAM_INT);
  607. $returnurl = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'userid'=>$userid, 'mode'=>$mode, 'offset'=>$offset, 'forcerefresh'=>1));
  608. // create but do not add student submission date
  609. $submission = $this->get_submission($userid, true, true);
  610. if (!data_submitted() or !$this->can_finalize($submission) or !confirm_sesskey()) {
  611. redirect($returnurl); // probably closed already
  612. }
  613. $updated = new stdClass();
  614. $updated->id = $submission->id;
  615. $updated->data2 = ASSIGNMENT_STATUS_CLOSED;
  616. $DB->update_record('assignment_submissions', $updated);
  617. add_to_log($this->course->id, 'assignment', 'upload', //TODO: add finalize action to log
  618. 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
  619. $submission = $this->get_submission($userid, false, true);
  620. $this->update_grade($submission);
  621. redirect($returnurl);
  622. }
  623. function unfinalize($forcemode=null) {
  624. global $DB;
  625. $userid = required_param('userid', PARAM_INT);
  626. $mode = required_param('mode', PARAM_ALPHA);
  627. $offset = required_param('offset', PARAM_INT);
  628. if ($forcemode!=null) {
  629. $mode=$forcemode;
  630. }
  631. $returnurl = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'userid'=>$userid, 'mode'=>$mode, 'offset'=>$offset, 'forcerefresh'=>1) );
  632. if (data_submitted()
  633. and $submission = $this->get_submission($userid)
  634. and $this->can_unfinalize($submission)
  635. and confirm_sesskey()) {
  636. $updated = new stdClass();
  637. $updated->id = $submission->id;
  638. $updated->data2 = '';
  639. $DB->update_record('assignment_submissions', $updated);
  640. //TODO: add unfinalize action to log
  641. 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);
  642. $submission = $this->get_submission($userid);
  643. $this->update_grade($submission);
  644. }
  645. if ($forcemode==null) {
  646. redirect($returnurl);
  647. }
  648. }
  649. function delete() {
  650. $action = optional_param('action', '', PARAM_ALPHA);
  651. switch ($action) {
  652. case 'response':
  653. $this->delete_responsefile();
  654. break;
  655. default:
  656. $this->delete_file();
  657. }
  658. die;
  659. }
  660. function delete_responsefile() {
  661. global $CFG, $OUTPUT,$PAGE;
  662. $file = required_param('file', PARAM_FILE);
  663. $userid = required_param('userid', PARAM_INT);
  664. $mode = required_param('mode', PARAM_ALPHA);
  665. $offset = required_param('offset', PARAM_INT);
  666. $confirm = optional_param('confirm', 0, PARAM_BOOL);
  667. $returnurl = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'userid'=>$userid, 'mode'=>$mode, 'offset'=>$offset));
  668. if (!$this->can_manage_responsefiles()) {
  669. redirect($returnurl);
  670. }
  671. $urlreturn = 'submissions.php';
  672. $optionsreturn = array('id'=>$this->cm->id, 'offset'=>$offset, 'mode'=>$mode, 'userid'=>$userid);
  673. if (!data_submitted() or !$confirm or !confirm_sesskey()) {
  674. $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'action'=>'response', 'mode'=>$mode, 'offset'=>$offset, 'sesskey'=>sesskey());
  675. $PAGE->set_title(get_string('delete'));
  676. echo $OUTPUT->header();
  677. echo $OUTPUT->heading(get_string('delete'));
  678. echo $OUTPUT->confirm(get_string('confirmdeletefile', 'assignment', $file), new moodle_url('delete.php', $optionsyes), new moodle_url($urlreturn, $optionsreturn));
  679. echo $OUTPUT->footer();
  680. die;
  681. }
  682. if ($submission = $this->get_submission($userid)) {
  683. $fs = get_file_storage();
  684. if ($file = $fs->get_file($this->context->id, 'mod_assignment', 'response', $submission->id, '/', $file)) {
  685. $file->delete();
  686. }
  687. }
  688. redirect($returnurl);
  689. }
  690. function delete_file() {
  691. global $CFG, $DB, $OUTPUT, $PAGE;
  692. $file = required_param('file', PARAM_FILE);
  693. $userid = required_param('userid', PARAM_INT);
  694. $confirm = optional_param('confirm', 0, PARAM_BOOL);
  695. $mode = optional_param('mode', '', PARAM_ALPHA);
  696. $offset = optional_param('offset', 0, PARAM_INT);
  697. require_login($this->course, false, $this->cm);
  698. if (empty($mode)) {
  699. $urlreturn = 'view.php';
  700. $optionsreturn = array('id'=>$this->cm->id);
  701. $returnurl = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  702. } else {
  703. $urlreturn = 'submissions.php';
  704. $optionsreturn = array('id'=>$this->cm->id, 'offset'=>$offset, 'mode'=>$mode, 'userid'=>$userid);
  705. $returnurl = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id, 'offset'=>$offset, 'userid'=>$userid));
  706. }
  707. if (!$submission = $this->get_submission($userid) // incorrect submission
  708. or !$this->can_delete_files($submission)) { // can not delete
  709. $this->view_header(get_string('delete'));
  710. echo $OUTPUT->notification(get_string('cannotdeletefiles', 'assignment'));
  711. echo $OUTPUT->continue_button($returnurl);
  712. $this->view_footer();
  713. die;
  714. }
  715. if (!data_submitted() or !$confirm or !confirm_sesskey()) {
  716. $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'sesskey'=>sesskey(), 'mode'=>$mode, 'offset'=>$offset, 'sesskey'=>sesskey());
  717. if (empty($mode)) {
  718. $this->view_header(get_string('delete'));
  719. } else {
  720. $PAGE->set_title(get_string('delete'));
  721. echo $OUTPUT->header();
  722. }
  723. echo $OUTPUT->heading(get_string('delete'));
  724. echo $OUTPUT->confirm(get_string('confirmdeletefile', 'assignment', $file), new moodle_url('delete.php', $optionsyes), new moodle_url($urlreturn, $optionsreturn));
  725. if (empty($mode)) {
  726. $this->view_footer();
  727. } else {
  728. echo $OUTPUT->footer();
  729. }
  730. die;
  731. }
  732. $fs = get_file_storage();
  733. if ($file = $fs->get_file($this->context->id, 'mod_assignment', 'submission', $submission->id, '/', $file)) {
  734. $file->delete();
  735. $submission->timemodified = time();
  736. $DB->update_record('assignment_submissions', $submission);
  737. add_to_log($this->course->id, 'assignment', 'upload', //TODO: add delete action to log
  738. 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
  739. $this->update_grade($submission);
  740. }
  741. redirect($returnurl);
  742. }
  743. function can_upload_file($submission) {
  744. global $USER;
  745. if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
  746. and $this->isopen() // assignment not closed yet
  747. and (empty($submission) or ($submission->userid == $USER->id)) // his/her own submission
  748. and !$this->is_finalized($submission)) { // no uploading after final submission
  749. return true;
  750. } else {
  751. return false;
  752. }
  753. }
  754. function can_manage_responsefiles() {
  755. if (has_capability('mod/assignment:grade', $this->context)) {
  756. return true;
  757. } else {
  758. return false;
  759. }
  760. }
  761. function can_delete_files($submission) {
  762. global $USER;
  763. if (has_capability('mod/assignment:grade', $this->context)) {
  764. return true;
  765. }
  766. if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
  767. and $this->isopen() // assignment not closed yet
  768. and $this->assignment->resubmit // deleting allowed
  769. and $USER->id == $submission->userid // his/her own submission
  770. and !$this->is_finalized($submission)) { // no deleting after final submission
  771. return true;
  772. } else {
  773. return false;
  774. }
  775. }
  776. function drafts_tracked() {
  777. return !empty($this->assignment->var4);
  778. }
  779. /**
  780. * Returns submission status
  781. * @param object $submission - may be empty
  782. * @return string submission state - empty, ASSIGNMENT_STATUS_SUBMITTED or ASSIGNMENT_STATUS_CLOSED
  783. */
  784. function is_finalized($submission) {
  785. if (!$this->drafts_tracked()) {
  786. return '';
  787. } else if (empty($submission)) {
  788. return '';
  789. } else if ($submission->data2 == ASSIGNMENT_STATUS_SUBMITTED or $submission->data2 == ASSIGNMENT_STATUS_CLOSED) {
  790. return $submission->data2;
  791. } else {
  792. return '';
  793. }
  794. }
  795. function can_unfinalize($submission) {
  796. if(is_bool($submission)) {
  797. return false;
  798. }
  799. if (!$this->drafts_tracked()) {
  800. return false;
  801. }
  802. if (has_capability('mod/assignment:grade', $this->context)
  803. and $this->isopen()
  804. and $this->is_finalized($submission)) {
  805. return true;
  806. } else {
  807. return false;
  808. }
  809. }
  810. function can_finalize($submission) {
  811. global $USER;
  812. if(is_bool($submission)) {
  813. return false;
  814. }
  815. if (!$this->drafts_tracked()) {
  816. return false;
  817. }
  818. if ($this->is_finalized($submission)) {
  819. return false;
  820. }
  821. if (has_capability('mod/assignment:grade', $this->context)) {
  822. return true;
  823. } else if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
  824. and $this->isopen() // assignment not closed yet
  825. and !empty($submission) // submission must exist
  826. and $submission->userid == $USER->id // his/her own submission
  827. and ($this->count_user_files($submission->id)
  828. or ($this->notes_allowed() and !empty($submission->data1)))) { // something must be submitted
  829. return true;
  830. } else {
  831. return false;
  832. }
  833. }
  834. function can_update_notes($submission) {
  835. global $USER;
  836. if (is_enrolled($this->context, $USER, 'mod/assignment:submit')
  837. and $this->notes_allowed() // notesd must be allowed
  838. and $this->isopen() // assignment not closed yet
  839. and (empty($submission) or $USER->id == $submission->userid) // his/her own submission
  840. and !$this->is_finalized($submission)) { // no updateingafter final submission
  841. return true;
  842. } else {
  843. return false;
  844. }
  845. }
  846. function notes_allowed() {
  847. return (boolean)$this->assignment->var2;
  848. }
  849. function count_responsefiles($userid) {
  850. if ($submission = $this->get_submission($userid)) {
  851. $fs = get_file_storage();
  852. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'response', $submission->id, "id", false);
  853. return count($files);
  854. } else {
  855. return 0;
  856. }
  857. }
  858. function setup_elements(&$mform) {
  859. global $CFG, $COURSE;
  860. $ynoptions = array( 0 => get_string('no'), 1 => get_string('yes'));
  861. $choices = get_max_upload_sizes($CFG->maxbytes, $COURSE->maxbytes);
  862. $choices[0] = get_string('courseuploadlimit') . ' ('.display_size($COURSE->maxbytes).')';
  863. $mform->addElement('select', 'maxbytes', get_string('maximumsize', 'assignment'), $choices);
  864. $mform->setDefault('maxbytes', $CFG->assignment_maxbytes);
  865. $mform->addElement('select', 'resubmit', get_string('allowdeleting', 'assignment'), $ynoptions);
  866. $mform->addHelpButton('resubmit', 'allowdeleting', 'assignment');
  867. $mform->setDefault('resubmit', 1);
  868. $options = array();
  869. for($i = 1; $i <= 20; $i++) {
  870. $options[$i] = $i;
  871. }
  872. $mform->addElement('select', 'var1', get_string('allowmaxfiles', 'assignment'), $options);
  873. $mform->addHelpButton('var1', 'allowmaxfiles', 'assignment');
  874. $mform->setDefault('var1', 3);
  875. $mform->addElement('select', 'var2', get_string('allownotes', 'assignment'), $ynoptions);
  876. $mform->addHelpButton('var2', 'allownotes', 'assignment');
  877. $mform->setDefault('var2', 0);
  878. $mform->addElement('select', 'var3', get_string('hideintro', 'assignment'), $ynoptions);
  879. $mform->addHelpButton('var3', 'hideintro', 'assignment');
  880. $mform->setDefault('var3', 0);
  881. $mform->addElement('select', 'emailteachers', get_string('emailteachers', 'assignment'), $ynoptions);
  882. $mform->addHelpButton('emailteachers', 'emailteachers', 'assignment');
  883. $mform->setDefault('emailteachers', 0);
  884. $mform->addElement('select', 'var4', get_string('trackdrafts', 'assignment'), $ynoptions);
  885. $mform->addHelpButton('var4', 'trackdrafts', 'assignment');
  886. $mform->setDefault('var4', 1);
  887. $course_context = get_context_instance(CONTEXT_COURSE, $COURSE->id);
  888. plagiarism_get_form_elements_module($mform, $course_context);
  889. }
  890. function portfolio_exportable() {
  891. return true;
  892. }
  893. function extend_settings_navigation($node) {
  894. global $CFG, $USER, $OUTPUT;
  895. // get users submission if there is one
  896. $submission = $this->get_submission();
  897. if (is_enrolled($this->context, $USER, 'mod/assignment:submit')) {
  898. $editable = $this->isopen() && (!$submission || $this->assignment->resubmit || !$submission->timemarked);
  899. } else {
  900. $editable = false;
  901. }
  902. // If the user has submitted something add some related links and data
  903. if (isset($submission->data2) AND $submission->data2 == 'submitted') {
  904. // Add a view link to the settings nav
  905. $link = new moodle_url('/mod/assignment/view.php', array('id'=>$this->cm->id));
  906. $node->add(get_string('viewmysubmission', 'assignment'), $link, navigation_node::TYPE_SETTING);
  907. if (!empty($submission->timemodified)) {
  908. $submittednode = $node->add(get_string('submitted', 'assignment') . ' ' . userdate($submission->timemodified));
  909. $submittednode->text = preg_replace('#([^,])\s#', '$1&nbsp;', $submittednode->text);
  910. $submittednode->add_class('note');
  911. if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
  912. $submittednode->add_class('early');
  913. } else {
  914. $submittednode->add_class('late');
  915. }
  916. }
  917. }
  918. // Check if the user has uploaded any files, if so we can add some more stuff to the settings nav
  919. if ($submission && is_enrolled($this->context, $USER, 'mod/assignment:submit') && $this->count_user_files($submission->id)) {
  920. $fs = get_file_storage();
  921. if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
  922. if (!$this->drafts_tracked() or !$this->isopen() or $this->is_finalized($submission)) {
  923. $filenode = $node->add(get_string('submission', 'assignment'));
  924. } else {
  925. $filenode = $node->add(get_string('submissiondraft', 'assignment'));
  926. }
  927. foreach ($files as $file) {
  928. $filename = $file->get_filename();
  929. $mimetype = $file->get_mimetype();
  930. $link = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
  931. $filenode->add($filename, $link, navigation_node::TYPE_SETTING, null, null, new pix_icon(file_file_icon($file),''));
  932. }
  933. }
  934. }
  935. // Show a notes link if they are enabled
  936. if ($this->notes_allowed()) {
  937. $link = new moodle_url('/mod/assignment/upload.php', array('id'=>$this->cm->id, 'action'=>'editnotes', 'sesskey'=>sesskey()));
  938. $node->add(get_string('notes', 'assignment'), $link);
  939. }
  940. }
  941. /**
  942. * creates a zip of all assignment submissions and sends a zip to the browser
  943. */
  944. public function download_submissions() {
  945. global $CFG,$DB;
  946. require_once($CFG->libdir.'/filelib.php');
  947. $submissions = $this->get_submissions('','');
  948. if (empty($submissions)) {
  949. print_error('errornosubmissions', 'assignment');
  950. }
  951. $filesforzipping = array();
  952. $fs = get_file_storage();
  953. $groupmode = groups_get_activity_groupmode($this->cm);
  954. $groupid = 0; // All users
  955. $groupname = '';
  956. if ($groupmode) {
  957. $groupid = groups_get_activity_group($this->cm, true);
  958. $groupname = groups_get_group_name($groupid).'-';
  959. }
  960. $filename = str_replace(' ', '_', clean_filename($this->course->shortname.'-'.$this->assignment->name.'-'.$groupname.$this->assignment->id.".zip")); //name of new zip file.
  961. foreach ($submissions as $submission) {
  962. $a_userid = $submission->userid; //get userid
  963. if ((groups_is_member($groupid,$a_userid)or !$groupmode or !$groupid)) {
  964. $a_assignid = $submission->assignment; //get name of this assignment for use in the file names.
  965. $a_user = $DB->get_record("user", array("id"=>$a_userid),'id,username,firstname,lastname'); //get user firstname/lastname
  966. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
  967. foreach ($files as $file) {
  968. //get files new name.
  969. $fileext = strstr($file->get_filename(), '.');
  970. $fileoriginal = str_replace($fileext, '', $file->get_filename());
  971. $fileforzipname = clean_filename(fullname($a_user) . "_" . $fileoriginal."_".$a_userid.$fileext);
  972. //save file name to array for zipping.
  973. $filesforzipping[$fileforzipname] = $file;
  974. }
  975. }
  976. } // end of foreach loop
  977. if ($zipfile = assignment_pack_files($filesforzipping)) {
  978. send_temp_file($zipfile, $filename); //send file and delete after sending.
  979. }
  980. }
  981. /**
  982. * Check the given submission is complete. Preliminary rows are often created in the assignment_submissions

Large files files are truncated, but you can click here to view the full file