PageRenderTime 35ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/assignment/lib.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 4085 lines | 3692 code | 138 blank | 255 comment | 117 complexity | feb5ca864c9abf7a00511e02e08f6c84 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0

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

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