PageRenderTime 70ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/mod/assignment/lib.php

https://bitbucket.org/ngmares/moodle
PHP | 4044 lines | 3655 code | 136 blank | 253 comment | 113 complexity | b86c1dc381560e0982af409e73504077 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, BSD-3-Clause

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_base is the base class for assignment types
  18. *
  19. * This class provides all the functionality for an assignment
  20. *
  21. * @package mod-assignment
  22. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24. */
  25. /** Include eventslib.php */
  26. require_once($CFG->libdir.'/eventslib.php');
  27. /** Include formslib.php */
  28. require_once($CFG->libdir.'/formslib.php');
  29. /** Include calendar/lib.php */
  30. require_once($CFG->dirroot.'/calendar/lib.php');
  31. /** ASSIGNMENT_COUNT_WORDS = 1 */
  32. define('ASSIGNMENT_COUNT_WORDS', 1);
  33. /** ASSIGNMENT_COUNT_LETTERS = 2 */
  34. define('ASSIGNMENT_COUNT_LETTERS', 2);
  35. /**
  36. * Standard base class for all assignment submodules (assignment types).
  37. *
  38. * @package mod-assignment
  39. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  40. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41. */
  42. class assignment_base {
  43. const FILTER_ALL = 0;
  44. const FILTER_SUBMITTED = 1;
  45. const FILTER_REQUIRE_GRADING = 2;
  46. /** @var object */
  47. var $cm;
  48. /** @var object */
  49. var $course;
  50. /** @var stdClass */
  51. var $coursecontext;
  52. /** @var object */
  53. var $assignment;
  54. /** @var string */
  55. var $strassignment;
  56. /** @var string */
  57. var $strassignments;
  58. /** @var string */
  59. var $strsubmissions;
  60. /** @var string */
  61. var $strlastmodified;
  62. /** @var string */
  63. var $pagetitle;
  64. /** @var bool */
  65. var $usehtmleditor;
  66. /**
  67. * @todo document this var
  68. */
  69. var $defaultformat;
  70. /**
  71. * @todo document this var
  72. */
  73. var $context;
  74. /** @var string */
  75. var $type;
  76. /**
  77. * Constructor for the base assignment class
  78. *
  79. * Constructor for the base assignment class.
  80. * If cmid is set create the cm, course, assignment objects.
  81. * If the assignment is hidden and the user is not a teacher then
  82. * this prints a page header and notice.
  83. *
  84. * @global object
  85. * @global object
  86. * @param int $cmid the current course module id - not set for new assignments
  87. * @param object $assignment usually null, but if we have it we pass it to save db access
  88. * @param object $cm usually null, but if we have it we pass it to save db access
  89. * @param object $course usually null, but if we have it we pass it to save db access
  90. */
  91. function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
  92. global $COURSE, $DB;
  93. if ($cmid == 'staticonly') {
  94. //use static functions only!
  95. return;
  96. }
  97. global $CFG;
  98. if ($cm) {
  99. $this->cm = $cm;
  100. } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
  101. print_error('invalidcoursemodule');
  102. }
  103. $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
  104. if ($course) {
  105. $this->course = $course;
  106. } else if ($this->cm->course == $COURSE->id) {
  107. $this->course = $COURSE;
  108. } else if (! $this->course = $DB->get_record('course', array('id'=>$this->cm->course))) {
  109. print_error('invalidid', 'assignment');
  110. }
  111. $this->coursecontext = get_context_instance(CONTEXT_COURSE, $this->course->id);
  112. $courseshortname = format_text($this->course->shortname, true, array('context' => $this->coursecontext));
  113. if ($assignment) {
  114. $this->assignment = $assignment;
  115. } else if (! $this->assignment = $DB->get_record('assignment', array('id'=>$this->cm->instance))) {
  116. print_error('invalidid', 'assignment');
  117. }
  118. $this->assignment->cmidnumber = $this->cm->idnumber; // compatibility with modedit assignment obj
  119. $this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj
  120. $this->strassignment = get_string('modulename', 'assignment');
  121. $this->strassignments = get_string('modulenameplural', 'assignment');
  122. $this->strsubmissions = get_string('submissions', 'assignment');
  123. $this->strlastmodified = get_string('lastmodified');
  124. $this->pagetitle = strip_tags($courseshortname.': '.$this->strassignment.': '.format_string($this->assignment->name, true, array('context' => $this->context)));
  125. // visibility handled by require_login() with $cm parameter
  126. // get current group only when really needed
  127. /// Set up things for a HTML editor if it's needed
  128. $this->defaultformat = editors_get_preferred_format();
  129. }
  130. /**
  131. * Display the assignment, used by view.php
  132. *
  133. * This in turn calls the methods producing individual parts of the page
  134. */
  135. function view() {
  136. $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
  137. require_capability('mod/assignment:view', $context);
  138. add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
  139. $this->assignment->id, $this->cm->id);
  140. $this->view_header();
  141. $this->view_intro();
  142. $this->view_dates();
  143. $this->view_feedback();
  144. $this->view_footer();
  145. }
  146. /**
  147. * Display the header and top of a page
  148. *
  149. * (this doesn't change much for assignment types)
  150. * This is used by the view() method to print the header of view.php but
  151. * it can be used on other pages in which case the string to denote the
  152. * page in the navigation trail should be passed as an argument
  153. *
  154. * @global object
  155. * @param string $subpage Description of subpage to be used in navigation trail
  156. */
  157. function view_header($subpage='') {
  158. global $CFG, $PAGE, $OUTPUT;
  159. if ($subpage) {
  160. $PAGE->navbar->add($subpage);
  161. }
  162. $PAGE->set_title($this->pagetitle);
  163. $PAGE->set_heading($this->course->fullname);
  164. echo $OUTPUT->header();
  165. groups_print_activity_menu($this->cm, $CFG->wwwroot . '/mod/assignment/view.php?id=' . $this->cm->id);
  166. echo '<div class="reportlink">'.$this->submittedlink().'</div>';
  167. echo '<div class="clearer"></div>';
  168. if (has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
  169. echo $OUTPUT->notification(get_string('upgradenotification', 'assignment'));
  170. $adminurl = new moodle_url('/admin/tool/assignmentupgrade/listnotupgraded.php');
  171. echo $OUTPUT->single_button($adminurl, get_string('viewassignmentupgradetool', 'assignment'));
  172. }
  173. }
  174. /**
  175. * Display the assignment intro
  176. *
  177. * This will most likely be extended by assignment type plug-ins
  178. * The default implementation prints the assignment description in a box
  179. */
  180. function view_intro() {
  181. global $OUTPUT;
  182. echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
  183. echo format_module_intro('assignment', $this->assignment, $this->cm->id);
  184. echo $OUTPUT->box_end();
  185. echo plagiarism_print_disclosure($this->cm->id);
  186. }
  187. /**
  188. * Display the assignment dates
  189. *
  190. * Prints the assignment start and end dates in a box.
  191. * This will be suitable for most assignment types
  192. */
  193. function view_dates() {
  194. global $OUTPUT;
  195. if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
  196. return;
  197. }
  198. echo $OUTPUT->box_start('generalbox boxaligncenter', 'dates');
  199. echo '<table>';
  200. if ($this->assignment->timeavailable) {
  201. echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
  202. echo ' <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
  203. }
  204. if ($this->assignment->timedue) {
  205. echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
  206. echo ' <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
  207. }
  208. echo '</table>';
  209. echo $OUTPUT->box_end();
  210. }
  211. /**
  212. * Display the bottom and footer of a page
  213. *
  214. * This default method just prints the footer.
  215. * This will be suitable for most assignment types
  216. */
  217. function view_footer() {
  218. global $OUTPUT;
  219. echo $OUTPUT->footer();
  220. }
  221. /**
  222. * Display the feedback to the student
  223. *
  224. * This default method prints the teacher picture and name, date when marked,
  225. * grade and teacher submissioncomment.
  226. * If advanced grading is used the method render_grade from the
  227. * advanced grading controller is called to display the grade.
  228. *
  229. * @global object
  230. * @global object
  231. * @global object
  232. * @param object $submission The submission object or NULL in which case it will be loaded
  233. */
  234. function view_feedback($submission=NULL) {
  235. global $USER, $CFG, $DB, $OUTPUT, $PAGE;
  236. require_once($CFG->libdir.'/gradelib.php');
  237. require_once("$CFG->dirroot/grade/grading/lib.php");
  238. if (!$submission) { /// Get submission for this assignment
  239. $userid = $USER->id;
  240. $submission = $this->get_submission($userid);
  241. } else {
  242. $userid = $submission->userid;
  243. }
  244. // Check the user can submit
  245. $canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
  246. // If not then check if the user still has the view cap and has a previous submission
  247. $canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
  248. // Or if user can grade (is a teacher or admin)
  249. $canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
  250. if (!$canviewfeedback) {
  251. // can not view or submit assignments -> no feedback
  252. return;
  253. }
  254. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
  255. $item = $grading_info->items[0];
  256. $grade = $item->grades[$userid];
  257. if ($grade->hidden or $grade->grade === false) { // hidden or error
  258. return;
  259. }
  260. if ($grade->grade === null and empty($grade->str_feedback)) { /// Nothing to show yet
  261. return;
  262. }
  263. $graded_date = $grade->dategraded;
  264. $graded_by = $grade->usermodified;
  265. /// We need the teacher info
  266. if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
  267. print_error('cannotfindteacher');
  268. }
  269. /// Print the feedback
  270. echo $OUTPUT->heading(get_string('feedbackfromteacher', 'assignment', fullname($teacher)));
  271. echo '<table cellspacing="0" class="feedback">';
  272. echo '<tr>';
  273. echo '<td class="left picture">';
  274. if ($teacher) {
  275. echo $OUTPUT->user_picture($teacher);
  276. }
  277. echo '</td>';
  278. echo '<td class="topic">';
  279. echo '<div class="from">';
  280. if ($teacher) {
  281. echo '<div class="fullname">'.fullname($teacher).'</div>';
  282. }
  283. echo '<div class="time">'.userdate($graded_date).'</div>';
  284. echo '</div>';
  285. echo '</td>';
  286. echo '</tr>';
  287. echo '<tr>';
  288. echo '<td class="left side">&nbsp;</td>';
  289. echo '<td class="content">';
  290. $gradestr = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
  291. if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
  292. $controller->set_grade_range(make_grades_menu($this->assignment->grade));
  293. echo $controller->render_grade($PAGE, $submission->id, $item, $gradestr, has_capability('mod/assignment:grade', $this->context));
  294. } else {
  295. echo $gradestr;
  296. }
  297. echo '<div class="clearer"></div>';
  298. echo '<div class="comment">';
  299. echo $grade->str_feedback;
  300. echo '</div>';
  301. echo '</tr>';
  302. if ($this->type == 'uploadsingle') { //@TODO: move to overload view_feedback method in the class or is uploadsingle merging into upload?
  303. $responsefiles = $this->print_responsefiles($submission->userid, true);
  304. if (!empty($responsefiles)) {
  305. echo '<tr>';
  306. echo '<td class="left side">&nbsp;</td>';
  307. echo '<td class="content">';
  308. echo $responsefiles;
  309. echo '</tr>';
  310. }
  311. }
  312. echo '</table>';
  313. }
  314. /**
  315. * Returns a link with info about the state of the assignment submissions
  316. *
  317. * This is used by view_header to put this link at the top right of the page.
  318. * For teachers it gives the number of submitted assignments with a link
  319. * For students it gives the time of their submission.
  320. * This will be suitable for most assignment types.
  321. *
  322. * @global object
  323. * @global object
  324. * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php
  325. * @return string
  326. */
  327. function submittedlink($allgroups=false) {
  328. global $USER;
  329. global $CFG;
  330. $submitted = '';
  331. $urlbase = "{$CFG->wwwroot}/mod/assignment/";
  332. $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
  333. if (has_capability('mod/assignment:grade', $context)) {
  334. if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) {
  335. $group = 0;
  336. } else {
  337. $group = groups_get_activity_group($this->cm);
  338. }
  339. if ($this->type == 'offline') {
  340. $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
  341. get_string('viewfeedback', 'assignment').'</a>';
  342. } else if ($count = $this->count_real_submissions($group)) {
  343. $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
  344. get_string('viewsubmissions', 'assignment', $count).'</a>';
  345. } else {
  346. $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
  347. get_string('noattempts', 'assignment').'</a>';
  348. }
  349. } else {
  350. if (isloggedin()) {
  351. if ($submission = $this->get_submission($USER->id)) {
  352. // If the submission has been completed
  353. if ($this->is_submitted_with_required_data($submission)) {
  354. if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
  355. $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
  356. } else {
  357. $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
  358. }
  359. }
  360. }
  361. }
  362. }
  363. return $submitted;
  364. }
  365. /**
  366. * Returns whether the assigment supports lateness information
  367. *
  368. * @return bool This assignment type supports lateness (true, default) or no (false)
  369. */
  370. function supports_lateness() {
  371. return true;
  372. }
  373. /**
  374. * @todo Document this function
  375. */
  376. function setup_elements(&$mform) {
  377. }
  378. /**
  379. * Any preprocessing needed for the settings form for
  380. * this assignment type
  381. *
  382. * @param array $default_values - array to fill in with the default values
  383. * in the form 'formelement' => 'value'
  384. * @param object $form - the form that is to be displayed
  385. * @return none
  386. */
  387. function form_data_preprocessing(&$default_values, $form) {
  388. }
  389. /**
  390. * Any extra validation checks needed for the settings
  391. * form for this assignment type
  392. *
  393. * See lib/formslib.php, 'validation' function for details
  394. */
  395. function form_validation($data, $files) {
  396. return array();
  397. }
  398. /**
  399. * Create a new assignment activity
  400. *
  401. * Given an object containing all the necessary data,
  402. * (defined by the form in mod_form.php) this function
  403. * will create a new instance and return the id number
  404. * of the new instance.
  405. * The due data is added to the calendar
  406. * This is common to all assignment types.
  407. *
  408. * @global object
  409. * @global object
  410. * @param object $assignment The data from the form on mod_form.php
  411. * @return int The id of the assignment
  412. */
  413. function add_instance($assignment) {
  414. global $COURSE, $DB;
  415. $assignment->timemodified = time();
  416. $assignment->courseid = $assignment->course;
  417. $returnid = $DB->insert_record("assignment", $assignment);
  418. $assignment->id = $returnid;
  419. if ($assignment->timedue) {
  420. $event = new stdClass();
  421. $event->name = $assignment->name;
  422. $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
  423. $event->courseid = $assignment->course;
  424. $event->groupid = 0;
  425. $event->userid = 0;
  426. $event->modulename = 'assignment';
  427. $event->instance = $returnid;
  428. $event->eventtype = 'due';
  429. $event->timestart = $assignment->timedue;
  430. $event->timeduration = 0;
  431. calendar_event::create($event);
  432. }
  433. assignment_grade_item_update($assignment);
  434. return $returnid;
  435. }
  436. /**
  437. * Deletes an assignment activity
  438. *
  439. * Deletes all database records, files and calendar events for this assignment.
  440. *
  441. * @global object
  442. * @global object
  443. * @param object $assignment The assignment to be deleted
  444. * @return boolean False indicates error
  445. */
  446. function delete_instance($assignment) {
  447. global $CFG, $DB;
  448. $assignment->courseid = $assignment->course;
  449. $result = true;
  450. // now get rid of all files
  451. $fs = get_file_storage();
  452. if ($cm = get_coursemodule_from_instance('assignment', $assignment->id)) {
  453. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  454. $fs->delete_area_files($context->id);
  455. }
  456. if (! $DB->delete_records('assignment_submissions', array('assignment'=>$assignment->id))) {
  457. $result = false;
  458. }
  459. if (! $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
  460. $result = false;
  461. }
  462. if (! $DB->delete_records('assignment', array('id'=>$assignment->id))) {
  463. $result = false;
  464. }
  465. $mod = $DB->get_field('modules','id',array('name'=>'assignment'));
  466. assignment_grade_item_delete($assignment);
  467. return $result;
  468. }
  469. /**
  470. * Updates a new assignment activity
  471. *
  472. * Given an object containing all the necessary data,
  473. * (defined by the form in mod_form.php) this function
  474. * will update the assignment instance and return the id number
  475. * The due date is updated in the calendar
  476. * This is common to all assignment types.
  477. *
  478. * @global object
  479. * @global object
  480. * @param object $assignment The data from the form on mod_form.php
  481. * @return bool success
  482. */
  483. function update_instance($assignment) {
  484. global $COURSE, $DB;
  485. $assignment->timemodified = time();
  486. $assignment->id = $assignment->instance;
  487. $assignment->courseid = $assignment->course;
  488. $DB->update_record('assignment', $assignment);
  489. if ($assignment->timedue) {
  490. $event = new stdClass();
  491. if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
  492. $event->name = $assignment->name;
  493. $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
  494. $event->timestart = $assignment->timedue;
  495. $calendarevent = calendar_event::load($event->id);
  496. $calendarevent->update($event);
  497. } else {
  498. $event = new stdClass();
  499. $event->name = $assignment->name;
  500. $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
  501. $event->courseid = $assignment->course;
  502. $event->groupid = 0;
  503. $event->userid = 0;
  504. $event->modulename = 'assignment';
  505. $event->instance = $assignment->id;
  506. $event->eventtype = 'due';
  507. $event->timestart = $assignment->timedue;
  508. $event->timeduration = 0;
  509. calendar_event::create($event);
  510. }
  511. } else {
  512. $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id));
  513. }
  514. // get existing grade item
  515. assignment_grade_item_update($assignment);
  516. return true;
  517. }
  518. /**
  519. * Update grade item for this submission.
  520. *
  521. * @param stdClass $submission The submission instance
  522. */
  523. function update_grade($submission) {
  524. assignment_update_grades($this->assignment, $submission->userid);
  525. }
  526. /**
  527. * Top-level function for handling of submissions called by submissions.php
  528. *
  529. * This is for handling the teacher interaction with the grading interface
  530. * This should be suitable for most assignment types.
  531. *
  532. * @global object
  533. * @param string $mode Specifies the kind of teacher interaction taking place
  534. */
  535. function submissions($mode) {
  536. ///The main switch is changed to facilitate
  537. ///1) Batch fast grading
  538. ///2) Skip to the next one on the popup
  539. ///3) Save and Skip to the next one on the popup
  540. //make user global so we can use the id
  541. global $USER, $OUTPUT, $DB, $PAGE;
  542. $mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
  543. if (optional_param('next', null, PARAM_BOOL)) {
  544. $mode='next';
  545. }
  546. if (optional_param('saveandnext', null, PARAM_BOOL)) {
  547. $mode='saveandnext';
  548. }
  549. if (is_null($mailinfo)) {
  550. if (optional_param('sesskey', null, PARAM_BOOL)) {
  551. set_user_preference('assignment_mailinfo', 0);
  552. } else {
  553. $mailinfo = get_user_preferences('assignment_mailinfo', 0);
  554. }
  555. } else {
  556. set_user_preference('assignment_mailinfo', $mailinfo);
  557. }
  558. if (!($this->validate_and_preprocess_feedback())) {
  559. // form was submitted ('Save' or 'Save and next' was pressed, but validation failed)
  560. $this->display_submission();
  561. return;
  562. }
  563. switch ($mode) {
  564. case 'grade': // We are in a main window grading
  565. if ($submission = $this->process_feedback()) {
  566. $this->display_submissions(get_string('changessaved'));
  567. } else {
  568. $this->display_submissions();
  569. }
  570. break;
  571. case 'single': // We are in a main window displaying one submission
  572. if ($submission = $this->process_feedback()) {
  573. $this->display_submissions(get_string('changessaved'));
  574. } else {
  575. $this->display_submission();
  576. }
  577. break;
  578. case 'all': // Main window, display everything
  579. $this->display_submissions();
  580. break;
  581. case 'fastgrade':
  582. ///do the fast grading stuff - this process should work for all 3 subclasses
  583. $grading = false;
  584. $commenting = false;
  585. $col = false;
  586. if (isset($_POST['submissioncomment'])) {
  587. $col = 'submissioncomment';
  588. $commenting = true;
  589. }
  590. if (isset($_POST['menu'])) {
  591. $col = 'menu';
  592. $grading = true;
  593. }
  594. if (!$col) {
  595. //both submissioncomment and grade columns collapsed..
  596. $this->display_submissions();
  597. break;
  598. }
  599. foreach ($_POST[$col] as $id => $unusedvalue){
  600. $id = (int)$id; //clean parameter name
  601. $this->process_outcomes($id);
  602. if (!$submission = $this->get_submission($id)) {
  603. $submission = $this->prepare_new_submission($id);
  604. $newsubmission = true;
  605. } else {
  606. $newsubmission = false;
  607. }
  608. unset($submission->data1); // Don't need to update this.
  609. unset($submission->data2); // Don't need to update this.
  610. //for fast grade, we need to check if any changes take place
  611. $updatedb = false;
  612. if ($grading) {
  613. $grade = $_POST['menu'][$id];
  614. $updatedb = $updatedb || ($submission->grade != $grade);
  615. $submission->grade = $grade;
  616. } else {
  617. if (!$newsubmission) {
  618. unset($submission->grade); // Don't need to update this.
  619. }
  620. }
  621. if ($commenting) {
  622. $commentvalue = trim($_POST['submissioncomment'][$id]);
  623. $updatedb = $updatedb || ($submission->submissioncomment != $commentvalue);
  624. $submission->submissioncomment = $commentvalue;
  625. } else {
  626. unset($submission->submissioncomment); // Don't need to update this.
  627. }
  628. $submission->teacher = $USER->id;
  629. if ($updatedb) {
  630. $submission->mailed = (int)(!$mailinfo);
  631. }
  632. $submission->timemarked = time();
  633. //if it is not an update, we don't change the last modified time etc.
  634. //this will also not write into database if no submissioncomment and grade is entered.
  635. if ($updatedb){
  636. if ($newsubmission) {
  637. if (!isset($submission->submissioncomment)) {
  638. $submission->submissioncomment = '';
  639. }
  640. $sid = $DB->insert_record('assignment_submissions', $submission);
  641. $submission->id = $sid;
  642. } else {
  643. $DB->update_record('assignment_submissions', $submission);
  644. }
  645. // trigger grade event
  646. $this->update_grade($submission);
  647. //add to log only if updating
  648. add_to_log($this->course->id, 'assignment', 'update grades',
  649. 'submissions.php?id='.$this->cm->id.'&user='.$submission->userid,
  650. $submission->userid, $this->cm->id);
  651. }
  652. }
  653. $message = $OUTPUT->notification(get_string('changessaved'), 'notifysuccess');
  654. $this->display_submissions($message);
  655. break;
  656. case 'saveandnext':
  657. ///We are in pop up. save the current one and go to the next one.
  658. //first we save the current changes
  659. if ($submission = $this->process_feedback()) {
  660. //print_heading(get_string('changessaved'));
  661. //$extra_javascript = $this->update_main_listing($submission);
  662. }
  663. case 'next':
  664. /// We are currently in pop up, but we want to skip to next one without saving.
  665. /// This turns out to be similar to a single case
  666. /// The URL used is for the next submission.
  667. $offset = required_param('offset', PARAM_INT);
  668. $nextid = required_param('nextid', PARAM_INT);
  669. $id = required_param('id', PARAM_INT);
  670. $filter = optional_param('filter', self::FILTER_ALL, PARAM_INT);
  671. if ($mode == 'next' || $filter !== self::FILTER_REQUIRE_GRADING) {
  672. $offset = (int)$offset+1;
  673. }
  674. $redirect = new moodle_url('submissions.php',
  675. array('id' => $id, 'offset' => $offset, 'userid' => $nextid,
  676. 'mode' => 'single', 'filter' => $filter));
  677. redirect($redirect);
  678. break;
  679. case 'singlenosave':
  680. $this->display_submission();
  681. break;
  682. default:
  683. echo "something seriously is wrong!!";
  684. break;
  685. }
  686. }
  687. /**
  688. * Checks if grading method allows quickgrade mode. At the moment it is hardcoded
  689. * that advanced grading methods do not allow quickgrade.
  690. *
  691. * Assignment type plugins are not allowed to override this method
  692. *
  693. * @return boolean
  694. */
  695. public final function quickgrade_mode_allowed() {
  696. global $CFG;
  697. require_once("$CFG->dirroot/grade/grading/lib.php");
  698. if ($controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
  699. return false;
  700. }
  701. return true;
  702. }
  703. /**
  704. * Helper method updating the listing on the main script from popup using javascript
  705. *
  706. * @global object
  707. * @global object
  708. * @param $submission object The submission whose data is to be updated on the main page
  709. */
  710. function update_main_listing($submission) {
  711. global $SESSION, $CFG, $OUTPUT;
  712. $output = '';
  713. $perpage = get_user_preferences('assignment_perpage', 10);
  714. $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
  715. /// Run some Javascript to try and update the parent page
  716. $output .= '<script type="text/javascript">'."\n<!--\n";
  717. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
  718. if ($quickgrade){
  719. $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
  720. .trim($submission->submissioncomment).'";'."\n";
  721. } else {
  722. $output.= 'opener.document.getElementById("com'.$submission->userid.
  723. '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
  724. }
  725. }
  726. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
  727. //echo optional_param('menuindex');
  728. if ($quickgrade){
  729. $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
  730. '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
  731. } else {
  732. $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
  733. $this->display_grade($submission->grade)."\";\n";
  734. }
  735. }
  736. //need to add student's assignments in there too.
  737. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
  738. $submission->timemodified) {
  739. $output.= 'opener.document.getElementById("ts'.$submission->userid.
  740. '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
  741. }
  742. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
  743. $submission->timemarked) {
  744. $output.= 'opener.document.getElementById("tt'.$submission->userid.
  745. '").innerHTML="'.userdate($submission->timemarked)."\";\n";
  746. }
  747. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
  748. $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
  749. $buttontext = get_string('update');
  750. $url = new moodle_url('/mod/assignment/submissions.php', array(
  751. 'id' => $this->cm->id,
  752. 'userid' => $submission->userid,
  753. 'mode' => 'single',
  754. 'offset' => (optional_param('offset', '', PARAM_INT)-1)));
  755. $button = $OUTPUT->action_link($url, $buttontext, new popup_action('click', $url, 'grade'.$submission->userid, array('height' => 450, 'width' => 700)), array('ttile'=>$buttontext));
  756. $output .= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
  757. }
  758. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
  759. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
  760. $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
  761. '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
  762. }
  763. if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
  764. if (!empty($grading_info->outcomes)) {
  765. foreach($grading_info->outcomes as $n=>$outcome) {
  766. if ($outcome->grades[$submission->userid]->locked) {
  767. continue;
  768. }
  769. if ($quickgrade){
  770. $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
  771. '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
  772. } else {
  773. $options = make_grades_menu(-$outcome->scaleid);
  774. $options[0] = get_string('nooutcome', 'grades');
  775. $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
  776. }
  777. }
  778. }
  779. }
  780. $output .= "\n-->\n</script>";
  781. return $output;
  782. }
  783. /**
  784. * Return a grade in user-friendly form, whether it's a scale or not
  785. *
  786. * @global object
  787. * @param mixed $grade
  788. * @return string User-friendly representation of grade
  789. */
  790. function display_grade($grade) {
  791. global $DB;
  792. static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
  793. if ($this->assignment->grade >= 0) { // Normal number
  794. if ($grade == -1) {
  795. return '-';
  796. } else {
  797. return $grade.' / '.$this->assignment->grade;
  798. }
  799. } else { // Scale
  800. if (empty($scalegrades[$this->assignment->id])) {
  801. if ($scale = $DB->get_record('scale', array('id'=>-($this->assignment->grade)))) {
  802. $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
  803. } else {
  804. return '-';
  805. }
  806. }
  807. if (isset($scalegrades[$this->assignment->id][$grade])) {
  808. return $scalegrades[$this->assignment->id][$grade];
  809. }
  810. return '-';
  811. }
  812. }
  813. /**
  814. * Display a single submission, ready for grading on a popup window
  815. *
  816. * This default method prints the teacher info and submissioncomment box at the top and
  817. * the student info and submission at the bottom.
  818. * This method also fetches the necessary data in order to be able to
  819. * provide a "Next submission" button.
  820. * Calls preprocess_submission() to give assignment type plug-ins a chance
  821. * to process submissions before they are graded
  822. * This method gets its arguments from the page parameters userid and offset
  823. *
  824. * @global object
  825. * @global object
  826. * @param string $extra_javascript
  827. */
  828. function display_submission($offset=-1,$userid =-1, $display=true) {
  829. global $CFG, $DB, $PAGE, $OUTPUT, $USER;
  830. require_once($CFG->libdir.'/gradelib.php');
  831. require_once($CFG->libdir.'/tablelib.php');
  832. require_once("$CFG->dirroot/repository/lib.php");
  833. require_once("$CFG->dirroot/grade/grading/lib.php");
  834. if ($userid==-1) {
  835. $userid = required_param('userid', PARAM_INT);
  836. }
  837. if ($offset==-1) {
  838. $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
  839. }
  840. $filter = optional_param('filter', 0, PARAM_INT);
  841. if (!$user = $DB->get_record('user', array('id'=>$userid))) {
  842. print_error('nousers');
  843. }
  844. if (!$submission = $this->get_submission($user->id)) {
  845. $submission = $this->prepare_new_submission($userid);
  846. }
  847. if ($submission->timemodified > $submission->timemarked) {
  848. $subtype = 'assignmentnew';
  849. } else {
  850. $subtype = 'assignmentold';
  851. }
  852. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
  853. $gradingdisabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
  854. /// construct SQL, using current offset to find the data of the next student
  855. $course = $this->course;
  856. $assignment = $this->assignment;
  857. $cm = $this->cm;
  858. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  859. //reset filter to all for offline assignment
  860. if ($assignment->assignmenttype == 'offline' && $filter == self::FILTER_SUBMITTED) {
  861. $filter = self::FILTER_ALL;
  862. }
  863. /// Get all ppl that can submit assignments
  864. $currentgroup = groups_get_activity_group($cm);
  865. $users = get_enrolled_users($context, 'mod/assignment:submit', $currentgroup, 'u.id');
  866. if ($users) {
  867. $users = array_keys($users);
  868. // if groupmembersonly used, remove users who are not in any group
  869. if (!empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
  870. if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
  871. $users = array_intersect($users, array_keys($groupingusers));
  872. }
  873. }
  874. }
  875. $nextid = 0;
  876. $where = '';
  877. if($filter == self::FILTER_SUBMITTED) {
  878. $where .= 's.timemodified > 0 AND ';
  879. } else if($filter == self::FILTER_REQUIRE_GRADING) {
  880. $where .= 's.timemarked < s.timemodified AND ';
  881. }
  882. if ($users) {
  883. $userfields = user_picture::fields('u', array('lastaccess'));
  884. $select = "SELECT $userfields,
  885. s.id AS submissionid, s.grade, s.submissioncomment,
  886. s.timemodified, s.timemarked,
  887. CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
  888. ELSE 0 END AS status ";
  889. $sql = 'FROM {user} u '.
  890. 'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
  891. AND s.assignment = '.$this->assignment->id.' '.
  892. 'WHERE '.$where.'u.id IN ('.implode(',', $users).') ';
  893. if ($sort = flexible_table::get_sort_for_table('mod-assignment-submissions')) {
  894. $sort = 'ORDER BY '.$sort.' ';
  895. }
  896. $auser = $DB->get_records_sql($select.$sql.$sort, null, $offset, 2);
  897. if (is_array($auser) && count($auser)>1) {
  898. $nextuser = next($auser);
  899. $nextid = $nextuser->id;
  900. }
  901. }
  902. if ($submission->teacher) {
  903. $teacher = $DB->get_record('user', array('id'=>$submission->teacher));
  904. } else {
  905. global $USER;
  906. $teacher = $USER;
  907. }
  908. $this->preprocess_submission($submission);
  909. $mformdata = new stdClass();
  910. $mformdata->context = $this->context;
  911. $mformdata->maxbytes = $this->course->maxbytes;
  912. $mformdata->courseid = $this->course->id;
  913. $mformdata->teacher = $teacher;
  914. $mformdata->assignment = $assignment;
  915. $mformdata->submission = $submission;
  916. $mformdata->lateness = $this->display_lateness($submission->timemodified);
  917. $mformdata->auser = $auser;
  918. $mformdata->user = $user;
  919. $mformdata->offset = $offset;
  920. $mformdata->userid = $userid;
  921. $mformdata->cm = $this->cm;
  922. $mformdata->grading_info = $grading_info;
  923. $mformdata->enableoutcomes = $CFG->enableoutcomes;
  924. $mformdata->grade = $this->assignment->grade;
  925. $mformdata->gradingdisabled = $gradingdisabled;
  926. $mformdata->nextid = $nextid;
  927. $mformdata->submissioncomment= $submission->submissioncomment;
  928. $mformdata->submissioncommentformat= FORMAT_HTML;
  929. $mformdata->submission_content= $this->print_user_files($user->id,true);
  930. $mformdata->filter = $filter;
  931. $mformdata->mailinfo = get_user_preferences('assignment_mailinfo', 0);
  932. if ($assignment->assignmenttype == 'upload') {
  933. $mformdata->fileui_options = array('subdirs'=>1, 'maxbytes'=>$assignment->maxbytes, 'maxfiles'=>$assignment->var1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
  934. } elseif ($assignment->assignmenttype == 'uploadsingle') {
  935. $mformdata->fileui_options = array('subdirs'=>0, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
  936. }
  937. $advancedgradingwarning = false;
  938. $gradingmanager = get_grading_manager($this->context, 'mod_assignment', 'submission');
  939. if ($gradingmethod = $gradingmanager->get_active_method()) {
  940. $controller = $gradingmanager->get_controller($gradingmethod);
  941. if ($controller->is_form_available()) {
  942. $itemid = null;
  943. if (!empty($submission->id)) {
  944. $itemid = $submission->id;
  945. }
  946. if ($gradingdisabled && $itemid) {
  947. $mformdata->advancedgradinginstance = $controller->get_current_instance($USER->id, $itemid);
  948. } else if (!$gradingdisabled) {
  949. $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
  950. $mformdata->advancedgradinginstance = $controller->get_or_create_instance($instanceid, $USER->id, $itemid);
  951. }
  952. } else {
  953. $advancedgradingwarning = $controller->form_unavailable_notification();
  954. }
  955. }
  956. $submitform = new assignment_grading_form( null, $mformdata );
  957. if (!$display) {
  958. $ret_data = new stdClass();
  959. $ret_data->mform = $submitform;
  960. if (isset($mformdata->fileui_options)) {
  961. $ret_data->fileui_options = $mformdata->fileui_options;
  962. }
  963. return $ret_data;
  964. }
  965. if ($submitform->is_cancelled()) {
  966. redirect('submissions.php?id='.$this->cm->id);
  967. }
  968. $submitform->set_data($mformdata);
  969. $PAGE->set_title($this->course->fullname . ': ' .get_string('feedback', 'assignment').' - '.fullname($user, true));
  970. $PAGE->set_heading($this->course->fullname);
  971. $PAGE->navbar->add(get_string('submissions', 'assignment'), new moodle_url('/mod/assignment/submissions.php', array('id'=>$cm->id)));
  972. $PAGE->navbar->add(fullname($user, true));
  973. echo $OUTPUT->header();
  974. echo $OUTPUT->heading(get_string('feedback', 'assignment').': '.fullname($user, true));
  975. // display mform here...
  976. if ($advancedgradingwarning) {
  977. echo $OUTPUT->notification($advancedgradingwarning, 'error');
  978. }
  979. $submitform->display();
  980. $customfeedback = $this->custom_feedbackform($submission, true);
  981. if (!empty($customfeedback)) {
  982. echo $customfeedback;
  983. }
  984. echo $OUTPUT->footer();
  985. }
  986. /**
  987. * Preprocess submission before grading
  988. *
  989. * Called by display_submission()
  990. * The default type does nothing here.
  991. *
  992. * @param object $submission The submission object
  993. */
  994. function preprocess_submission(&$submission) {
  995. }
  996. /**
  997. * Display all the submissions ready for grading
  998. *
  999. * @global object
  1000. * @global object
  1001. * @global object
  1002. * @global object
  1003. * @param string $message
  1004. * @return bool|void
  1005. */
  1006. function display_submissions($message='') {
  1007. global $CFG, $DB, $USER, $DB, $OUTPUT, $PAGE;
  1008. require_once($CFG->libdir.'/gradelib.php');
  1009. /* first we check to see if the form has just been submitted
  1010. * to request user_preference updates
  1011. */
  1012. $filters = array(self::FILTER_ALL => get_string('all'),
  1013. self::FILTER_REQUIRE_GRADING => get_string('requiregrading', 'assignment'));
  1014. $updatepref = optional_param('updatepref', 0, PARAM_BOOL);
  1015. if ($updatepref) {
  1016. $perpage = optional_param('perpage', 10, PARAM_INT);
  1017. $perpage = ($perpage <= 0) ? 10 : $perpage ;
  1018. $filter = optional_param('filter', 0, PARAM_INT);
  1019. set_user_preference('assignment_perpage', $perpage);
  1020. set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL));
  1021. set_user_preference('assignment_filter', $filter);
  1022. }
  1023. /* next we get perpage and quickgrade (allow quick grade) params
  1024. * from database
  1025. */
  1026. $perpage = get_user_preferences('assignment_perpage', 10);
  1027. $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
  1028. $filter = get_user_preferences('assignment_filter', 0);
  1029. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
  1030. if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
  1031. $uses_outcomes = true;
  1032. } else {
  1033. $uses_outcomes = false;
  1034. }
  1035. $page = optional_param('page', 0, PARAM_INT);
  1036. $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
  1037. /// Some shortcuts to make the code read better
  1038. $course = $this->course;
  1039. $assignment = $this->assignment;
  1040. $cm = $this->cm;
  1041. $hassubmission = false;
  1042. // reset filter to all for offline assignment only.
  1043. if ($assignment->assignmenttype == 'offline') {
  1044. if ($filter == self::FILTER_SUBMITTED) {
  1045. $filter = self::FILTER_ALL;
  1046. }
  1047. } else {
  1048. $filters[self::FILTER_SUBMITTED] = get_string('submitted', 'assignment');
  1049. }
  1050. $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
  1051. add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id, $this->assignment->id, $this->cm->id);
  1052. $PAGE->set_title(format_string($this->assignment->name,true));
  1053. $PAGE->set_heading($this->course->fullname);
  1054. echo $OUTPUT->header();
  1055. echo '<div class="usersubmissions">';
  1056. //hook to allow plagiarism plugins to update status/print links.
  1057. echo plagiarism_update_status($this->course, $this->cm);
  1058. $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
  1059. if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
  1060. echo '<div class="allcoursegrades"><a href="' . $CFG->wwwroot . '/grade/report/grader/index.php?id=' . $course->id . '">'
  1061. . get_string('seeallcoursegrades', 'grades') . '</a></div>';
  1062. }
  1063. if (!empty($message)) {
  1064. echo $message; // display messages here if any
  1065. }
  1066. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  1067. /// Check to see if groups are being used in this assignment
  1068. /// find out current groups mode
  1069. $groupmode = groups_get_activity_groupmode($cm);
  1070. $currentgroup = groups_get_activity_group($cm, true);
  1071. groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/assignment/submissions.php?id=' . $this->cm->id);
  1072. /// Print quickgrade form around the table

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