PageRenderTime 76ms CodeModel.GetById 19ms RepoModel.GetById 1ms 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
  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 messages here if any
  1078. }
  1079. $context = context_module::instance($cm->id);
  1080. /// Check to see if groups are being used in this assignment
  1081. /// find out current groups mode
  1082. $groupmode = groups_get_activity_groupmode($cm);
  1083. $currentgroup = groups_get_activity_group($cm, true);
  1084. groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/assignment/submissions.php?id=' . $this->cm->id);
  1085. /// Print quickgrade form around the table
  1086. if ($quickgrade) {
  1087. $formattrs = array();
  1088. $formattrs['action'] = new moodle_url('/mod/assignment/submissions.php');
  1089. $formattrs['id'] = 'fastg';
  1090. $formattrs['method'] = 'post';
  1091. echo html_writer::start_tag('form', $formattrs);
  1092. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'id', 'value'=> $this->cm->id));
  1093. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'mode', 'value'=> 'fastgrade'));
  1094. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'page', 'value'=> $page));
  1095. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=> sesskey()));
  1096. }
  1097. /// Get all ppl that are allowed to submit assignments
  1098. list($esql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $currentgroup);
  1099. if ($filter == self::FILTER_ALL) {
  1100. $sql = "SELECT u.id FROM {user} u ".
  1101. "LEFT JOIN ($esql) eu ON eu.id=u.id ".
  1102. "WHERE u.deleted = 0 AND eu.id=u.id ";
  1103. } else {
  1104. $wherefilter = ' AND s.assignment = '. $this->assignment->id;
  1105. $assignmentsubmission = "LEFT JOIN {assignment_submissions} s ON (u.id = s.userid) ";
  1106. if($filter == self::FILTER_SUBMITTED) {
  1107. $wherefilter .= ' AND s.timemodified > 0 ';
  1108. } else if($filter == self::FILTER_REQUIRE_GRADING && $assignment->assignmenttype != 'offline') {
  1109. $wherefilter .= ' AND s.timemarked < s.timemodified ';
  1110. } else { // require grading for offline assignment
  1111. $assignmentsubmission = "";
  1112. $wherefilter = "";
  1113. }
  1114. $sql = "SELECT u.id FROM {user} u ".
  1115. "LEFT JOIN ($esql) eu ON eu.id=u.id ".
  1116. $assignmentsubmission.
  1117. "WHERE u.deleted = 0 AND eu.id=u.id ".
  1118. $wherefilter;
  1119. }
  1120. $users = $DB->get_records_sql($sql, $params);
  1121. if (!empty($users)) {
  1122. if($assignment->assignmenttype == 'offline' && $filter == self::FILTER_REQUIRE_GRADING) {
  1123. //remove users who has submitted their assignment
  1124. foreach ($this->get_submissions() as $submission) {
  1125. if (array_key_exists($submission->userid, $users)) {
  1126. unset($users[$submission->userid]);
  1127. }
  1128. }
  1129. }
  1130. $users = array_keys($users);
  1131. }
  1132. // if groupmembersonly used, remove users who are not in any group
  1133. if ($users and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
  1134. if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
  1135. $users = array_intersect($users, array_keys($groupingusers));
  1136. }
  1137. }
  1138. $extrafields = get_extra_user_fields($context);
  1139. $tablecolumns = array_merge(array('picture', 'fullname'), $extrafields,
  1140. array('grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade'));
  1141. if ($uses_outcomes) {
  1142. $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
  1143. }
  1144. $extrafieldnames = array();
  1145. foreach ($extrafields as $field) {
  1146. $extrafieldnames[] = get_user_field_name($field);
  1147. }
  1148. $tableheaders = array_merge(
  1149. array('', get_string('fullnameuser')),
  1150. $extrafieldnames,
  1151. array(
  1152. get_string('grade'),
  1153. get_string('comment', 'assignment'),
  1154. get_string('lastmodified').' ('.get_string('submission', 'assignment').')',
  1155. get_string('lastmodified').' ('.get_string('grade').')',
  1156. get_string('status'),
  1157. get_string('finalgrade', 'grades'),
  1158. ));
  1159. if ($uses_outcomes) {
  1160. $tableheaders[] = get_string('outcome', 'grades');
  1161. }
  1162. require_once($CFG->libdir.'/tablelib.php');
  1163. $table = new flexible_table('mod-assignment-submissions');
  1164. $table->define_columns($tablecolumns);
  1165. $table->define_headers($tableheaders);
  1166. $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
  1167. $table->sortable(true, 'lastname');//sorted by lastname by default
  1168. $table->collapsible(true);
  1169. $table->initialbars(true);
  1170. $table->column_suppress('picture');
  1171. $table->column_suppress('fullname');
  1172. $table->column_class('picture', 'picture');
  1173. $table->column_class('fullname', 'fullname');
  1174. foreach ($extrafields as $field) {
  1175. $table->column_class($field, $field);
  1176. }
  1177. $table->column_class('grade', 'grade');
  1178. $table->column_class('submissioncomment', 'comment');
  1179. $table->column_class('timemodified', 'timemodified');
  1180. $table->column_class('timemarked', 'timemarked');
  1181. $table->column_class('status', 'status');
  1182. $table->column_class('finalgrade', 'finalgrade');
  1183. if ($uses_outcomes) {
  1184. $table->column_class('outcome', 'outcome');
  1185. }
  1186. $table->set_attribute('cellspacing', '0');
  1187. $table->set_attribute('id', 'attempts');
  1188. $table->set_attribute('class', 'submissions');
  1189. $table->set_attribute('width', '100%');
  1190. $table->no_sorting('finalgrade');
  1191. $table->no_sorting('outcome');
  1192. $table->text_sorting('submissioncomment');
  1193. // Start working -- this is necessary as soon as the niceties are over
  1194. $table->setup();
  1195. /// Construct the SQL
  1196. list($where, $params) = $table->get_sql_where();
  1197. if ($where) {
  1198. $where .= ' AND ';
  1199. }
  1200. if ($filter == self::FILTER_SUBMITTED) {
  1201. $where .= 's.timemodified > 0 AND ';
  1202. } else if($filter == self::FILTER_REQUIRE_GRADING) {
  1203. $where = '';
  1204. if ($assignment->assignmenttype != 'offline') {
  1205. $where .= 's.timemarked < s.timemodified AND ';
  1206. }
  1207. }
  1208. if ($sort = $table->get_sql_sort()) {
  1209. $sort = ' ORDER BY '.$sort;
  1210. }
  1211. $ufields = user_picture::fields('u', $extrafields);
  1212. if (!empty($users)) {
  1213. $select = "SELECT $ufields,
  1214. s.id AS submissionid, s.grade, s.submissioncomment,
  1215. s.timemodified, s.timemarked,
  1216. CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
  1217. ELSE 0 END AS status ";
  1218. $sql = 'FROM {user} u '.
  1219. 'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
  1220. AND s.assignment = '.$this->assignment->id.' '.
  1221. 'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
  1222. $ausers = $DB->get_records_sql($select.$sql.$sort, $params, $table->get_page_start(), $table->get_page_size());
  1223. $table->pagesize($perpage, count($users));
  1224. ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
  1225. $offset = $page * $perpage;
  1226. $strupdate = get_string('update');
  1227. $strgrade = get_string('grade');
  1228. $strview = get_string('view');
  1229. $grademenu = make_grades_menu($this->assignment->grade);
  1230. if ($ausers !== false) {
  1231. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
  1232. $endposition = $offset + $perpage;
  1233. $currentposition = 0;
  1234. foreach ($ausers as $auser) {
  1235. if ($currentposition == $offset && $offset < $endposition) {
  1236. $rowclass = null;
  1237. $final_grade = $grading_info->items[0]->grades[$auser->id];
  1238. $grademax = $grading_info->items[0]->grademax;
  1239. $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
  1240. $locked_overridden = 'locked';
  1241. if ($final_grade->overridden) {
  1242. $locked_overridden = 'overridden';
  1243. }
  1244. // TODO add here code if advanced grading grade must be reviewed => $auser->status=0
  1245. $picture = $OUTPUT->user_picture($auser);
  1246. if (empty($auser->submissionid)) {
  1247. $auser->grade = -1; //no submission yet
  1248. }
  1249. if (!empty($auser->submissionid)) {
  1250. $hassubmission = true;
  1251. ///Prints student answer and student modified date
  1252. ///attach file or print link to student answer, depending on the type of the assignment.
  1253. ///Refer to print_student_answer in inherited classes.
  1254. if ($auser->timemodified > 0) {
  1255. $studentmodifiedcontent = $this->print_student_answer($auser->id)
  1256. . userdate($auser->timemodified);
  1257. if ($assignment->timedue && $auser->timemodified > $assignment->timedue && $this->supports_lateness()) {
  1258. $studentmodifiedcontent .= $this->display_lateness($auser->timemodified);
  1259. $rowclass = 'late';
  1260. }
  1261. } else {
  1262. $studentmodifiedcontent = '&nbsp;';
  1263. }
  1264. $studentmodified = html_writer::tag('div', $studentmodifiedcontent, array('id' => 'ts' . $auser->id));
  1265. ///Print grade, dropdown or text
  1266. if ($auser->timemarked > 0) {
  1267. $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
  1268. if ($final_grade->locked or $final_grade->overridden) {
  1269. $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
  1270. } else if ($quickgrade) {
  1271. $attributes = array();
  1272. $attributes['tabindex'] = $tabindex++;
  1273. $menu = html_writer::label(get_string('assignment:grade', 'assignment'), 'menumenu'. $auser->id, false, array('class' => 'accesshide'));
  1274. $menu .= html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
  1275. $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
  1276. } else {
  1277. $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
  1278. }
  1279. } else {
  1280. $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
  1281. if ($final_grade->locked or $final_grade->overridden) {
  1282. $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
  1283. } else if ($quickgrade) {
  1284. $attributes = array();
  1285. $attributes['tabindex'] = $tabindex++;
  1286. $menu = html_writer::label(get_string('assignment:grade', 'assignment'), 'menumenu'. $auser->id, false, array('class' => 'accesshide'));
  1287. $menu .= html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
  1288. $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
  1289. } else {
  1290. $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
  1291. }
  1292. }
  1293. ///Print Comment
  1294. if ($final_grade->locked or $final_grade->overridden) {
  1295. $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
  1296. } else if ($quickgrade) {
  1297. $comment = '<div id="com'.$auser->id.'">'
  1298. . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
  1299. . $auser->id.'" rows="2" cols="20" spellcheck="true">'.($auser->submissioncomment).'</textarea></div>';
  1300. } else {
  1301. $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
  1302. }
  1303. } else {
  1304. $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
  1305. $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
  1306. $status = '<div id="st'.$auser->id.'">&nbsp;</div>';
  1307. if ($final_grade->locked or $final_grade->overridden) {
  1308. $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
  1309. $hassubmission = true;
  1310. } else if ($quickgrade) { // allow editing
  1311. $attributes = array();
  1312. $attributes['tabindex'] = $tabindex++;
  1313. $menu = html_writer::label(get_string('assignment:grade', 'assignment'), 'menumenu'. $auser->id, false, array('class' => 'accesshide'));
  1314. $menu .= html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
  1315. $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
  1316. $hassubmission = true;
  1317. } else {
  1318. $grade = '<div id="g'.$auser->id.'">-</div>';
  1319. }
  1320. if ($final_grade->locked or $final_grade->overridden) {
  1321. $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
  1322. } else if ($quickgrade) {
  1323. $comment = '<div id="com'.$auser->id.'">'
  1324. . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
  1325. . $auser->id.'" rows="2" cols="20" spellcheck="true">'.($auser->submissioncomment).'</textarea></div>';
  1326. } else {
  1327. $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
  1328. }
  1329. }
  1330. if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
  1331. $auser->status = 0;
  1332. } else {
  1333. $auser->status = 1;
  1334. }
  1335. $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
  1336. if ($final_grade->locked or $final_grade->overridden) {
  1337. $buttontext = $strview;
  1338. }
  1339. ///No more buttons, we use popups ;-).
  1340. $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
  1341. . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;filter='.$filter.'&amp;offset='.$offset++;
  1342. $button = $OUTPUT->action_link($popup_url, $buttontext);
  1343. $status = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
  1344. $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
  1345. $outcomes = '';
  1346. if ($uses_outcomes) {
  1347. foreach($grading_info->outcomes as $n=>$outcome) {
  1348. $outcomes .= '<div class="outcome"><label for="'. 'outcome_'.$n.'_'.$auser->id .'">'.$outcome->name.'</label>';
  1349. $options = make_grades_menu(-$outcome->scaleid);
  1350. if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
  1351. $options[0] = get_string('nooutcome', 'grades');
  1352. $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
  1353. } else {
  1354. $attributes = array();
  1355. $attributes['tabindex'] = $tabindex++;
  1356. $attributes['id'] = 'outcome_'.$n.'_'.$auser->id;
  1357. $outcomes .= ' '.html_writer::select($options, 'outcome_'.$n.'['.$auser->id.']', $outcome->grades[$auser->id]->grade, array(0=>get_string('nooutcome', 'grades')), $attributes);
  1358. }
  1359. $outcomes .= '</div>';
  1360. }
  1361. }
  1362. $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&amp;course=' . $course->id . '">' . fullname($auser, has_capability('moodle/site:viewfullnames', $this->context)) . '</a>';
  1363. $extradata = array();
  1364. foreach ($extrafields as $field) {
  1365. $extradata[] = $auser->{$field};
  1366. }
  1367. $row = array_merge(array($picture, $userlink), $extradata,
  1368. array($grade, $comment, $studentmodified, $teachermodified,
  1369. $status, $finalgrade));
  1370. if ($uses_outcomes) {
  1371. $row[] = $outcomes;
  1372. }
  1373. $table->add_data($row, $rowclass);
  1374. }
  1375. $currentposition++;
  1376. }
  1377. if ($hassubmission && method_exists($this, 'download_submissions')) {
  1378. echo html_writer::start_tag('div', array('class' => 'mod-assignment-download-link'));
  1379. echo html_writer::link(new moodle_url('/mod/assignment/submissions.php', array('id' => $this->cm->id, 'download' => 'zip')), get_string('downloadall', 'assignment'));
  1380. echo html_writer::end_tag('div');
  1381. }
  1382. $table->print_html(); /// Print the whole table
  1383. } else {
  1384. if ($filter == self::FILTER_SUBMITTED) {
  1385. echo html_writer::tag('div', get_string('nosubmisson', 'assignment'), array('class'=>'nosubmisson'));
  1386. } else if ($filter == self::FILTER_REQUIRE_GRADING) {
  1387. echo html_writer::tag('div', get_string('norequiregrading', 'assignment'), array('class'=>'norequiregrading'));
  1388. }
  1389. }
  1390. }
  1391. /// Print quickgrade form around the table
  1392. if ($quickgrade && $table->started_output && !empty($users)){
  1393. $mailinfopref = false;
  1394. if (get_user_preferences('assignment_mailinfo', 1)) {
  1395. $mailinfopref = true;
  1396. }
  1397. $emailnotification = html_writer::checkbox('mailinfo', 1, $mailinfopref, get_string('enablenotification','assignment'));
  1398. $emailnotification .= $OUTPUT->help_icon('enablenotification', 'assignment');
  1399. echo html_writer::tag('div', $emailnotification, array('class'=>'emailnotification'));
  1400. $savefeedback = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'fastg', 'value'=>get_string('saveallfeedback', 'assignment')));
  1401. echo html_writer::tag('div', $savefeedback, array('class'=>'fastgbutton'));
  1402. echo html_writer::end_tag('form');
  1403. } else if ($quickgrade) {
  1404. echo html_writer::end_tag('form');
  1405. }
  1406. echo '</div>';
  1407. /// End of fast grading form
  1408. /// Mini form for setting user preference
  1409. $formaction = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id));
  1410. $mform = new MoodleQuickForm('optionspref', 'post', $formaction, '', array('class'=>'optionspref'));
  1411. $mform->addElement('hidden', 'updatepref');
  1412. $mform->setDefault('updatepref', 1);
  1413. $mform->addElement('header', 'qgprefs', get_string('optionalsettings', 'assignment'));
  1414. $mform->addElement('select', 'filter', get_string('show'), $filters);
  1415. $mform->setDefault('filter', $filter);
  1416. $mform->addElement('text', 'perpage', get_string('pagesize', 'assignment'), array('size'=>1));
  1417. $mform->setDefault('perpage', $perpage);
  1418. if ($this->quickgrade_mode_allowed()) {
  1419. $mform->addElement('checkbox', 'quickgrade', get_string('quickgrade','assignment'));
  1420. $mform->setDefault('quickgrade', $quickgrade);
  1421. $mform->addHelpButton('quickgrade', 'quickgrade', 'assignment');
  1422. }
  1423. $mform->addElement('submit', 'savepreferences', get_string('savepreferences'));
  1424. $mform->display();
  1425. echo $OUTPUT->footer();
  1426. }
  1427. /**
  1428. * If the form was cancelled ('Cancel' or 'Next' was pressed), call cancel method
  1429. * from advanced grading (if applicable) and returns true
  1430. * If the form was submitted, validates it and returns false if validation did not pass.
  1431. * If validation passes, preprocess advanced grading (if applicable) and returns true.
  1432. *
  1433. * Note to the developers: This is NOT the correct way to implement advanced grading
  1434. * in grading form. The assignment grading was written long time ago and unfortunately
  1435. * does not fully use the mforms. Usually function is_validated() is called to
  1436. * validate the form and get_data() is called to get the data from the form.
  1437. *
  1438. * Here we have to push the calculated grade to $_POST['xgrade'] because further processing
  1439. * of the form gets the data not from form->get_data(), but from $_POST (using statement
  1440. * like $feedback = data_submitted() )
  1441. */
  1442. protected function validate_and_preprocess_feedback() {
  1443. global $USER, $CFG;
  1444. require_once($CFG->libdir.'/gradelib.php');
  1445. if (!($feedback = data_submitted()) || !isset($feedback->userid) || !isset($feedback->offset)) {
  1446. return true; // No incoming data, nothing to validate
  1447. }
  1448. $userid = required_param('userid', PARAM_INT);
  1449. $offset = required_param('offset', PARAM_INT);
  1450. $gradinginfo = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($userid));
  1451. $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
  1452. if ($gradingdisabled) {
  1453. return true;
  1454. }
  1455. $submissiondata = $this->display_submission($offset, $userid, false);
  1456. $mform = $submissiondata->mform;
  1457. $gradinginstance = $mform->use_advanced_grading();
  1458. if (optional_param('cancel', false, PARAM_BOOL) || optional_param('next', false, PARAM_BOOL)) {
  1459. // form was cancelled
  1460. if ($gradinginstance) {
  1461. $gradinginstance->cancel();
  1462. }
  1463. } else if ($mform->is_submitted()) {
  1464. // form was submitted (= a submit button other than 'cancel' or 'next' has been clicked)
  1465. if (!$mform->is_validated()) {
  1466. return false;
  1467. }
  1468. // preprocess advanced grading here
  1469. if ($gradinginstance) {
  1470. $data = $mform->get_data();
  1471. // create submission if it did not exist yet because we need submission->id for storing the grading instance
  1472. $submission = $this->get_submission($userid, true);
  1473. $_POST['xgrade'] = $gradinginstance->submit_and_get_grade($data->advancedgrading, $submission->id);
  1474. }
  1475. }
  1476. return true;
  1477. }
  1478. /**
  1479. * Process teacher feedback submission
  1480. *
  1481. * This is called by submissions() when a grading even has taken place.
  1482. * It gets its data from the submitted form.
  1483. *
  1484. * @global object
  1485. * @global object
  1486. * @global object
  1487. * @return object|bool The updated submission object or false
  1488. */
  1489. function process_feedback($formdata=null) {
  1490. global $CFG, $USER, $DB;
  1491. require_once($CFG->libdir.'/gradelib.php');
  1492. if (!$feedback = data_submitted() or !confirm_sesskey()) { // No incoming data?
  1493. return false;
  1494. }
  1495. ///For save and next, we need to know the userid to save, and the userid to go
  1496. ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
  1497. ///as the userid to store
  1498. if ((int)$feedback->saveuserid !== -1){
  1499. $feedback->userid = $feedback->saveuserid;
  1500. }
  1501. if (!empty($feedback->cancel)) { // User hit cancel button
  1502. return false;
  1503. }
  1504. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
  1505. // store outcomes if needed
  1506. $this->process_outcomes($feedback->userid);
  1507. $submission = $this->get_submission($feedback->userid, true); // Get or make one
  1508. if (!($grading_info->items[0]->grades[$feedback->userid]->locked ||
  1509. $grading_info->items[0]->grades[$feedback->userid]->overridden) ) {
  1510. $submission->grade = $feedback->xgrade;
  1511. $submission->submissioncomment = $feedback->submissioncomment_editor['text'];
  1512. $submission->teacher = $USER->id;
  1513. $mailinfo = get_user_preferences('assignment_mailinfo', 0);
  1514. if (!$mailinfo) {
  1515. $submission->mailed = 1; // treat as already mailed
  1516. } else {
  1517. $submission->mailed = 0; // Make sure mail goes out (again, even)
  1518. }
  1519. $submission->timemarked = time();
  1520. unset($submission->data1); // Don't need to update this.
  1521. unset($submission->data2); // Don't need to update this.
  1522. if (empty($submission->timemodified)) { // eg for offline assignments
  1523. // $submission->timemodified = time();
  1524. }
  1525. $DB->update_record('assignment_submissions', $submission);
  1526. // triger grade event
  1527. $this->update_grade($submission);
  1528. add_to_log($this->course->id, 'assignment', 'update grades',
  1529. 'submissions.php?id='.$this->cm->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
  1530. if (!is_null($formdata)) {
  1531. if ($this->type == 'upload' || $this->type == 'uploadsingle') {
  1532. $mformdata = $formdata->mform->get_data();
  1533. $mformdata = file_postupdate_standard_filemanager($mformdata, 'files', $formdata->fileui_options, $this->context, 'mod_assignment', 'response', $submission->id);
  1534. }
  1535. }
  1536. }
  1537. return $submission;
  1538. }
  1539. function process_outcomes($userid) {
  1540. global $CFG, $USER;
  1541. if (empty($CFG->enableoutcomes)) {
  1542. return;
  1543. }
  1544. require_once($CFG->libdir.'/gradelib.php');
  1545. if (!$formdata = data_submitted() or !confirm_sesskey()) {
  1546. return;
  1547. }
  1548. $data = array();
  1549. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
  1550. if (!empty($grading_info->outcomes)) {
  1551. foreach($grading_info->outcomes as $n=>$old) {
  1552. $name = 'outcome_'.$n;
  1553. if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
  1554. $data[$n] = $formdata->{$name}[$userid];
  1555. }
  1556. }
  1557. }
  1558. if (count($data) > 0) {
  1559. grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
  1560. }
  1561. }
  1562. /**
  1563. * Load the submission object for a particular user
  1564. *
  1565. * @global object
  1566. * @global object
  1567. * @param int $userid int The id of the user whose submission we want or 0 in which case USER->id is used
  1568. * @param bool $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
  1569. * @param bool $teachermodified student submission set if false
  1570. * @return object|bool The submission or false (if $createnew is false and there is no existing submission).
  1571. */
  1572. function get_submission($userid=0, $createnew=false, $teachermodified=false) {
  1573. global $USER, $DB;
  1574. if (empty($userid)) {
  1575. $userid = $USER->id;
  1576. }
  1577. $submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
  1578. if ($submission) {
  1579. return $submission;
  1580. } else if (!$createnew) {
  1581. return false;
  1582. }
  1583. $newsubmission = $this->prepare_new_submission($userid, $teachermodified);
  1584. $DB->insert_record("assignment_submissions", $newsubmission);
  1585. return $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
  1586. }
  1587. /**
  1588. * Check the given submission is complete. Preliminary rows are often created in the assignment_submissions
  1589. * table before a submission actually takes place. This function checks to see if the given submission has actually
  1590. * been submitted.
  1591. *
  1592. * @param stdClass $submission The submission we want to check for completion
  1593. * @return bool Indicates if the submission was found to be complete
  1594. */
  1595. public function is_submitted_with_required_data($submission) {
  1596. return $submission->timemodified;
  1597. }
  1598. /**
  1599. * Instantiates a new submission object for a given user
  1600. *
  1601. * Sets the assignment, userid and times, everything else is set to default values.
  1602. *
  1603. * @param int $userid The userid for which we want a submission object
  1604. * @param bool $teachermodified student submission set if false
  1605. * @return object The submission
  1606. */
  1607. function prepare_new_submission($userid, $teachermodified=false) {
  1608. $submission = new stdClass();
  1609. $submission->assignment = $this->assignment->id;
  1610. $submission->userid = $userid;
  1611. $submission->timecreated = time();
  1612. // teachers should not be modifying modified date, except offline assignments
  1613. if ($teachermodified) {
  1614. $submission->timemodified = 0;
  1615. } else {
  1616. $submission->timemodified = $submission->timecreated;
  1617. }
  1618. $submission->numfiles = 0;
  1619. $submission->data1 = '';
  1620. $submission->data2 = '';
  1621. $submission->grade = -1;
  1622. $submission->submissioncomment = '';
  1623. $submission->format = 0;
  1624. $submission->teacher = 0;
  1625. $submission->timemarked = 0;
  1626. $submission->mailed = 0;
  1627. return $submission;
  1628. }
  1629. /**
  1630. * Return all assignment submissions by ENROLLED students (even empty)
  1631. *
  1632. * @param string $sort optional field names for the ORDER BY in the sql query
  1633. * @param string $dir optional specifying the sort direction, defaults to DESC
  1634. * @return array The submission objects indexed by id
  1635. */
  1636. function get_submissions($sort='', $dir='DESC') {
  1637. return assignment_get_all_submissions($this->assignment, $sort, $dir);
  1638. }
  1639. /**
  1640. * Counts all complete (real) assignment submissions by enrolled students
  1641. *
  1642. * @param int $groupid (optional) If nonzero then count is restricted to this group
  1643. * @return int The number of submissions
  1644. */
  1645. function count_real_submissions($groupid=0) {
  1646. global $CFG;
  1647. global $DB;
  1648. // Grab the context assocated with our course module
  1649. $context = context_module::instance($this->cm->id);
  1650. // Get ids of users enrolled in the given course.
  1651. list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $groupid);
  1652. $params['assignmentid'] = $this->cm->instance;
  1653. // Get ids of users enrolled in the given course.
  1654. return $DB->count_records_sql("SELECT COUNT('x')
  1655. FROM {assignment_submissions} s
  1656. LEFT JOIN {assignment} a ON a.id = s.assignment
  1657. INNER JOIN ($enroledsql) u ON u.id = s.userid
  1658. WHERE s.assignment = :assignmentid AND
  1659. s.timemodified > 0", $params);
  1660. }
  1661. /**
  1662. * Alerts teachers by email of new or changed assignments that need grading
  1663. *
  1664. * First checks whether the option to email teachers is set for this assignment.
  1665. * Sends an email to ALL teachers in the course (or in the group if using separate groups).
  1666. * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
  1667. *
  1668. * @global object
  1669. * @global object
  1670. * @param $submission object The submission that has changed
  1671. * @return void
  1672. */
  1673. function email_teachers($submission) {
  1674. global $CFG, $DB;
  1675. if (empty($this->assignment->emailteachers)) { // No need to do anything
  1676. return;
  1677. }
  1678. $user = $DB->get_record('user', array('id'=>$submission->userid));
  1679. if ($teachers = $this->get_graders($user)) {
  1680. $strassignments = get_string('modulenameplural', 'assignment');
  1681. $strassignment = get_string('modulename', 'assignment');
  1682. $strsubmitted = get_string('submitted', 'assignment');
  1683. foreach ($teachers as $teacher) {
  1684. $info = new stdClass();
  1685. $info->username = fullname($user, true);
  1686. $info->assignment = format_string($this->assignment->name,true);
  1687. $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
  1688. $info->timeupdated = userdate($submission->timemodified, '%c', $teacher->timezone);
  1689. $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
  1690. $posttext = $this->email_teachers_text($info);
  1691. $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
  1692. $eventdata = new stdClass();
  1693. $eventdata->modulename = 'assignment';
  1694. $eventdata->userfrom = $user;
  1695. $eventdata->userto = $teacher;
  1696. $eventdata->subject = $postsubject;
  1697. $eventdata->fullmessage = $posttext;
  1698. $eventdata->fullmessageformat = FORMAT_PLAIN;
  1699. $eventdata->fullmessagehtml = $posthtml;
  1700. $eventdata->smallmessage = $postsubject;
  1701. $eventdata->name = 'assignment_updates';
  1702. $eventdata->component = 'mod_assignment';
  1703. $eventdata->notification = 1;
  1704. $eventdata->contexturl = $info->url;
  1705. $eventdata->contexturlname = $info->assignment;
  1706. message_send($eventdata);
  1707. }
  1708. }
  1709. }
  1710. /**
  1711. * Sends a file
  1712. *
  1713. * @param string $filearea
  1714. * @param array $args
  1715. * @param bool $forcedownload whether or not force download
  1716. * @param array $options additional options affecting the file serving
  1717. * @return bool
  1718. */
  1719. function send_file($filearea, $args, $forcedownload, array $options=array()) {
  1720. debugging('plugin does not implement file sending', DEBUG_DEVELOPER);
  1721. return false;
  1722. }
  1723. /**
  1724. * Returns a list of teachers that should be grading given submission
  1725. *
  1726. * @param object $user
  1727. * @return array
  1728. */
  1729. function get_graders($user) {
  1730. global $DB;
  1731. //potential graders
  1732. list($enrolledsql, $params) = get_enrolled_sql($this->context, 'mod/assignment:grade', 0, true);
  1733. $sql = "SELECT u.*
  1734. FROM {user} u
  1735. JOIN ($enrolledsql) je ON je.id = u.id";
  1736. $potgraders = $DB->get_records_sql($sql, $params);
  1737. $graders = array();
  1738. if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) { // Separate groups are being used
  1739. if ($groups = groups_get_all_groups($this->course->id, $user->id)) { // Try to find all groups
  1740. foreach ($groups as $group) {
  1741. foreach ($potgraders as $t) {
  1742. if ($t->id == $user->id) {
  1743. continue; // do not send self
  1744. }
  1745. if (groups_is_member($group->id, $t->id)) {
  1746. $graders[$t->id] = $t;
  1747. }
  1748. }
  1749. }
  1750. } else {
  1751. // user not in group, try to find graders without group
  1752. foreach ($potgraders as $t) {
  1753. if ($t->id == $user->id) {
  1754. continue; // do not send self
  1755. }
  1756. if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
  1757. $graders[$t->id] = $t;
  1758. }
  1759. }
  1760. }
  1761. } else {
  1762. foreach ($potgraders as $t) {
  1763. if ($t->id == $user->id) {
  1764. continue; // do not send self
  1765. }
  1766. $graders[$t->id] = $t;
  1767. }
  1768. }
  1769. return $graders;
  1770. }
  1771. /**
  1772. * Creates the text content for emails to teachers
  1773. *
  1774. * @param $info object The info used by the 'emailteachermail' language string
  1775. * @return string
  1776. */
  1777. function email_teachers_text($info) {
  1778. $posttext = format_string($this->course->shortname, true, array('context' => $this->coursecontext)).' -> '.
  1779. $this->strassignments.' -> '.
  1780. format_string($this->assignment->name, true, array('context' => $this->context))."\n";
  1781. $posttext .= '---------------------------------------------------------------------'."\n";
  1782. $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
  1783. $posttext .= "\n---------------------------------------------------------------------\n";
  1784. return $posttext;
  1785. }
  1786. /**
  1787. * Creates the html content for emails to teachers
  1788. *
  1789. * @param $info object The info used by the 'emailteachermailhtml' language string
  1790. * @return string
  1791. */
  1792. function email_teachers_html($info) {
  1793. global $CFG;
  1794. $posthtml = '<p><font face="sans-serif">'.
  1795. '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname, true, array('context' => $this->coursecontext)).'</a> ->'.
  1796. '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
  1797. '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name, true, array('context' => $this->context)).'</a></font></p>';
  1798. $posthtml .= '<hr /><font face="sans-serif">';
  1799. $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
  1800. $posthtml .= '</font><hr />';
  1801. return $posthtml;
  1802. }
  1803. /**
  1804. * Produces a list of links to the files uploaded by a user
  1805. *
  1806. * @param $userid int optional id of the user. If 0 then $USER->id is used.
  1807. * @param $return boolean optional defaults to false. If true the list is returned rather than printed
  1808. * @return string optional
  1809. */
  1810. function print_user_files($userid=0, $return=false) {
  1811. global $CFG, $USER, $OUTPUT;
  1812. if (!$userid) {
  1813. if (!isloggedin()) {
  1814. return '';
  1815. }
  1816. $userid = $USER->id;
  1817. }
  1818. $output = '';
  1819. $submission = $this->get_submission($userid);
  1820. if (!$submission) {
  1821. return $output;
  1822. }
  1823. $fs = get_file_storage();
  1824. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
  1825. if (!empty($files)) {
  1826. require_once($CFG->dirroot . '/mod/assignment/locallib.php');
  1827. if ($CFG->enableportfolios) {
  1828. require_once($CFG->libdir.'/portfoliolib.php');
  1829. $button = new portfolio_add_button();
  1830. }
  1831. foreach ($files as $file) {
  1832. $filename = $file->get_filename();
  1833. $mimetype = $file->get_mimetype();
  1834. $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
  1835. $output .= '<a href="'.$path.'" >'.$OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon')).s($filename).'</a>';
  1836. if ($CFG->enableportfolios && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
  1837. $button->set_callback_options('assignment_portfolio_caller',
  1838. array('id' => $this->cm->id, 'submissionid' => $submission->id, 'fileid' => $file->get_id()),
  1839. 'mod_assignment');
  1840. $button->set_format_by_file($file);
  1841. $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
  1842. }
  1843. if ($CFG->enableplagiarism) {
  1844. require_once($CFG->libdir.'/plagiarismlib.php');
  1845. $output .= plagiarism_get_links(array('userid'=>$userid, 'file'=>$file, 'cmid'=>$this->cm->id, 'course'=>$this->course, 'assignment'=>$this->assignment));
  1846. $output .= '<br />';
  1847. }
  1848. }
  1849. if ($CFG->enableportfolios && count($files) > 1 && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
  1850. $button->set_callback_options('assignment_portfolio_caller',
  1851. array('id' => $this->cm->id, 'submissionid' => $submission->id),
  1852. 'mod_assignment');
  1853. $output .= '<br />' . $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
  1854. }
  1855. }
  1856. $output = '<div class="files">'.$output.'</div>';
  1857. if ($return) {
  1858. return $output;
  1859. }
  1860. echo $output;
  1861. }
  1862. /**
  1863. * Count the files uploaded by a given user
  1864. *
  1865. * @param $itemid int The submission's id as the file's itemid.
  1866. * @return int
  1867. */
  1868. function count_user_files($itemid) {
  1869. $fs = get_file_storage();
  1870. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $itemid, "id", false);
  1871. return count($files);
  1872. }
  1873. /**
  1874. * Returns true if the student is allowed to submit
  1875. *
  1876. * Checks that the assignment has started and, if the option to prevent late
  1877. * submissions is set, also checks that the assignment has not yet closed.
  1878. * @return boolean
  1879. */
  1880. function isopen() {
  1881. $time = time();
  1882. if ($this->assignment->preventlate && $this->assignment->timedue) {
  1883. return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
  1884. } else {
  1885. return ($this->assignment->timeavailable <= $time);
  1886. }
  1887. }
  1888. /**
  1889. * Return true if is set description is hidden till available date
  1890. *
  1891. * This is needed by calendar so that hidden descriptions do not
  1892. * come up in upcoming events.
  1893. *
  1894. * Check that description is hidden till available date
  1895. * By default return false
  1896. * Assignments types should implement this method if needed
  1897. * @return boolen
  1898. */
  1899. function description_is_hidden() {
  1900. return false;
  1901. }
  1902. /**
  1903. * Return an outline of the user's interaction with the assignment
  1904. *
  1905. * The default method prints the grade and timemodified
  1906. * @param $grade object
  1907. * @return object with properties ->info and ->time
  1908. */
  1909. function user_outline($grade) {
  1910. $result = new stdClass();
  1911. $result->info = get_string('grade').': '.$grade->str_long_grade;
  1912. $result->time = $grade->dategraded;
  1913. return $result;
  1914. }
  1915. /**
  1916. * Print complete information about the user's interaction with the assignment
  1917. *
  1918. * @param $user object
  1919. */
  1920. function user_complete($user, $grade=null) {
  1921. global $OUTPUT;
  1922. if ($submission = $this->get_submission($user->id)) {
  1923. $fs = get_file_storage();
  1924. if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
  1925. $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
  1926. foreach ($files as $file) {
  1927. $countfiles .= "; ".$file->get_filename();
  1928. }
  1929. }
  1930. echo $OUTPUT->box_start();
  1931. echo get_string("lastmodified").": ";
  1932. echo userdate($submission->timemodified);
  1933. echo $this->display_lateness($submission->timemodified);
  1934. $this->print_user_files($user->id);
  1935. echo '<br />';
  1936. $this->view_feedback($submission);
  1937. echo $OUTPUT->box_end();
  1938. } else {
  1939. if ($grade) {
  1940. echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
  1941. if ($grade->str_feedback) {
  1942. echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
  1943. }
  1944. }
  1945. print_string("notsubmittedyet", "assignment");
  1946. }
  1947. }
  1948. /**
  1949. * Return a string indicating how late a submission is
  1950. *
  1951. * @param $timesubmitted int
  1952. * @return string
  1953. */
  1954. function display_lateness($timesubmitted) {
  1955. return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
  1956. }
  1957. /**
  1958. * Empty method stub for all delete actions.
  1959. */
  1960. function delete() {
  1961. //nothing by default
  1962. redirect('view.php?id='.$this->cm->id);
  1963. }
  1964. /**
  1965. * Empty custom feedback grading form.
  1966. */
  1967. function custom_feedbackform($submission, $return=false) {
  1968. //nothing by default
  1969. return '';
  1970. }
  1971. /**
  1972. * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
  1973. * for the course (see resource).
  1974. *
  1975. * Given a course_module object, this function returns any "extra" information that may be needed
  1976. * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
  1977. *
  1978. * @param $coursemodule object The coursemodule object (record).
  1979. * @return cached_cm_info Object used to customise appearance on course page
  1980. */
  1981. function get_coursemodule_info($coursemodule) {
  1982. return null;
  1983. }
  1984. /**
  1985. * Plugin cron method - do not use $this here, create new assignment instances if needed.
  1986. * @return void
  1987. */
  1988. function cron() {
  1989. //no plugin cron by default - override if needed
  1990. }
  1991. /**
  1992. * Reset all submissions
  1993. */
  1994. function reset_userdata($data) {
  1995. global $CFG, $DB;
  1996. if (!$DB->count_records('assignment', array('course'=>$data->courseid, 'assignmenttype'=>$this->type))) {
  1997. return array(); // no assignments of this type present
  1998. }
  1999. $componentstr = get_string('modulenameplural', 'assignment');
  2000. $status = array();
  2001. $typestr = get_string('type'.$this->type, 'assignment');
  2002. // ugly hack to support pluggable assignment type titles...
  2003. if($typestr === '[[type'.$this->type.']]'){
  2004. $typestr = get_string('type'.$this->type, 'assignment_'.$this->type);
  2005. }
  2006. if (!empty($data->reset_assignment_submissions)) {
  2007. $assignmentssql = "SELECT a.id
  2008. FROM {assignment} a
  2009. WHERE a.course=? AND a.assignmenttype=?";
  2010. $params = array($data->courseid, $this->type);
  2011. // now get rid of all submissions and responses
  2012. $fs = get_file_storage();
  2013. if ($assignments = $DB->get_records_sql($assignmentssql, $params)) {
  2014. foreach ($assignments as $assignmentid=>$unused) {
  2015. if (!$cm = get_coursemodule_from_instance('assignment', $assignmentid)) {
  2016. continue;
  2017. }
  2018. $context = context_module::instance($cm->id);
  2019. $fs->delete_area_files($context->id, 'mod_assignment', 'submission');
  2020. $fs->delete_area_files($context->id, 'mod_assignment', 'response');
  2021. }
  2022. }
  2023. $DB->delete_records_select('assignment_submissions', "assignment IN ($assignmentssql)", $params);
  2024. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallsubmissions','assignment').': '.$typestr, 'error'=>false);
  2025. if (empty($data->reset_gradebook_grades)) {
  2026. // remove all grades from gradebook
  2027. assignment_reset_gradebook($data->courseid, $this->type);
  2028. }
  2029. }
  2030. return $status;
  2031. }
  2032. function portfolio_exportable() {
  2033. return false;
  2034. }
  2035. /**
  2036. * base implementation for backing up subtype specific information
  2037. * for one single module
  2038. *
  2039. * @param filehandle $bf file handle for xml file to write to
  2040. * @param mixed $preferences the complete backup preference object
  2041. *
  2042. * @return boolean
  2043. *
  2044. * @static
  2045. */
  2046. static function backup_one_mod($bf, $preferences, $assignment) {
  2047. return true;
  2048. }
  2049. /**
  2050. * base implementation for backing up subtype specific information
  2051. * for one single submission
  2052. *
  2053. * @param filehandle $bf file handle for xml file to write to
  2054. * @param mixed $preferences the complete backup preference object
  2055. * @param object $submission the assignment submission db record
  2056. *
  2057. * @return boolean
  2058. *
  2059. * @static
  2060. */
  2061. static function backup_one_submission($bf, $preferences, $assignment, $submission) {
  2062. return true;
  2063. }
  2064. /**
  2065. * base implementation for restoring subtype specific information
  2066. * for one single module
  2067. *
  2068. * @param array $info the array representing the xml
  2069. * @param object $restore the restore preferences
  2070. *
  2071. * @return boolean
  2072. *
  2073. * @static
  2074. */
  2075. static function restore_one_mod($info, $restore, $assignment) {
  2076. return true;
  2077. }
  2078. /**
  2079. * base implementation for restoring subtype specific information
  2080. * for one single submission
  2081. *
  2082. * @param object $submission the newly created submission
  2083. * @param array $info the array representing the xml
  2084. * @param object $restore the restore preferences
  2085. *
  2086. * @return boolean
  2087. *
  2088. * @static
  2089. */
  2090. static function restore_one_submission($info, $restore, $assignment, $submission) {
  2091. return true;
  2092. }
  2093. } ////// End of the assignment_base class
  2094. class assignment_grading_form extends moodleform {
  2095. /** @var stores the advaned grading instance (if used in grading) */
  2096. private $advancegradinginstance;
  2097. function definition() {
  2098. global $OUTPUT;
  2099. $mform =& $this->_form;
  2100. if (isset($this->_customdata->advancedgradinginstance)) {
  2101. $this->use_advanced_grading($this->_customdata->advancedgradinginstance);
  2102. }
  2103. $formattr = $mform->getAttributes();
  2104. $formattr['id'] = 'submitform';
  2105. $mform->setAttributes($formattr);
  2106. // hidden params
  2107. $mform->addElement('hidden', 'offset', ($this->_customdata->offset+1));
  2108. $mform->setType('offset', PARAM_INT);
  2109. $mform->addElement('hidden', 'userid', $this->_customdata->userid);
  2110. $mform->setType('userid', PARAM_INT);
  2111. $mform->addElement('hidden', 'nextid', $this->_customdata->nextid);
  2112. $mform->setType('nextid', PARAM_INT);
  2113. $mform->addElement('hidden', 'id', $this->_customdata->cm->id);
  2114. $mform->setType('id', PARAM_INT);
  2115. $mform->addElement('hidden', 'sesskey', sesskey());
  2116. $mform->setType('sesskey', PARAM_ALPHANUM);
  2117. $mform->addElement('hidden', 'mode', 'grade');
  2118. $mform->setType('mode', PARAM_TEXT);
  2119. $mform->addElement('hidden', 'menuindex', "0");
  2120. $mform->setType('menuindex', PARAM_INT);
  2121. $mform->addElement('hidden', 'saveuserid', "-1");
  2122. $mform->setType('saveuserid', PARAM_INT);
  2123. $mform->addElement('hidden', 'filter', "0");
  2124. $mform->setType('filter', PARAM_INT);
  2125. $mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->user),
  2126. fullname($this->_customdata->user, true) . '<br/>' .
  2127. userdate($this->_customdata->submission->timemodified) .
  2128. $this->_customdata->lateness );
  2129. $this->add_submission_content();
  2130. $this->add_grades_section();
  2131. $this->add_feedback_section();
  2132. if ($this->_customdata->submission->timemarked) {
  2133. $datestring = userdate($this->_customdata->submission->timemarked)."&nbsp; (".format_time(time() - $this->_customdata->submission->timemarked).")";
  2134. $mform->addElement('header', 'Last Grade', get_string('lastgrade', 'assignment'));
  2135. $mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->teacher) ,
  2136. fullname($this->_customdata->teacher,true).
  2137. '<br/>'.$datestring);
  2138. }
  2139. // buttons
  2140. $this->add_action_buttons();
  2141. }
  2142. /**
  2143. * Gets or sets the instance for advanced grading
  2144. *
  2145. * @param gradingform_instance $gradinginstance
  2146. */
  2147. public function use_advanced_grading($gradinginstance = false) {
  2148. if ($gradinginstance !== false) {
  2149. $this->advancegradinginstance = $gradinginstance;
  2150. }
  2151. return $this->advancegradinginstance;
  2152. }
  2153. /**
  2154. * Add the grades configuration section to the assignment configuration form
  2155. */
  2156. function add_grades_section() {
  2157. global $CFG;
  2158. $mform =& $this->_form;
  2159. $attributes = array();
  2160. if ($this->_customdata->gradingdisabled) {
  2161. $attributes['disabled'] ='disabled';
  2162. }
  2163. $mform->addElement('header', 'Grades', get_string('grades', 'grades'));
  2164. $grademenu = make_grades_menu($this->_customdata->assignment->grade);
  2165. if ($gradinginstance = $this->use_advanced_grading()) {
  2166. $gradinginstance->get_controller()->set_grade_range($grademenu);
  2167. $gradingelement = $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
  2168. if ($this->_customdata->gradingdisabled) {
  2169. $gradingelement->freeze();
  2170. } else {
  2171. $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
  2172. }
  2173. } else {
  2174. // use simple direct grading
  2175. $grademenu['-1'] = get_string('nograde');
  2176. $mform->addElement('select', 'xgrade', get_string('grade').':', $grademenu, $attributes);
  2177. $mform->setDefault('xgrade', $this->_customdata->submission->grade ); //@fixme some bug when element called 'grade' makes it break
  2178. $mform->setType('xgrade', PARAM_INT);
  2179. }
  2180. if (!empty($this->_customdata->enableoutcomes)) {
  2181. foreach($this->_customdata->grading_info->outcomes as $n=>$outcome) {
  2182. $options = make_grades_menu(-$outcome->scaleid);
  2183. if ($outcome->grades[$this->_customdata->submission->userid]->locked) {
  2184. $options[0] = get_string('nooutcome', 'grades');
  2185. $mform->addElement('static', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':',
  2186. $options[$outcome->grades[$this->_customdata->submission->userid]->grade]);
  2187. } else {
  2188. $options[''] = get_string('nooutcome', 'grades');
  2189. $attributes = array('id' => 'menuoutcome_'.$n );
  2190. $mform->addElement('select', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':', $options, $attributes );
  2191. $mform->setType('outcome_'.$n.'['.$this->_customdata->userid.']', PARAM_INT);
  2192. $mform->setDefault('outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->grades[$this->_customdata->submission->userid]->grade );
  2193. }
  2194. }
  2195. }
  2196. $course_context = context_module::instance($this->_customdata->cm->id);
  2197. if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
  2198. $grade = '<a href="'.$CFG->wwwroot.'/grade/report/grader/index.php?id='. $this->_customdata->courseid .'" >'.
  2199. $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade . '</a>';
  2200. }else{
  2201. $grade = $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade;
  2202. }
  2203. $mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assignment').':' ,$grade);
  2204. $mform->setType('finalgrade', PARAM_INT);
  2205. }
  2206. /**
  2207. *
  2208. * @global core_renderer $OUTPUT
  2209. */
  2210. function add_feedback_section() {
  2211. global $OUTPUT;
  2212. $mform =& $this->_form;
  2213. $mform->addElement('header', 'Feed Back', get_string('feedback', 'grades'));
  2214. if ($this->_customdata->gradingdisabled) {
  2215. $mform->addElement('static', 'disabledfeedback', $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_feedback );
  2216. } else {
  2217. // visible elements
  2218. $mform->addElement('editor', 'submissioncomment_editor', get_string('feedback', 'assignment').':', null, $this->get_editor_options() );
  2219. $mform->setType('submissioncomment_editor', PARAM_RAW); // to be cleaned before display
  2220. $mform->setDefault('submissioncomment_editor', $this->_customdata->submission->submissioncomment);
  2221. //$mform->addRule('submissioncomment', get_string('required'), 'required', null, 'client');
  2222. switch ($this->_customdata->assignment->assignmenttype) {
  2223. case 'upload' :
  2224. case 'uploadsingle' :
  2225. $mform->addElement('filemanager', 'files_filemanager', get_string('responsefiles', 'assignment'). ':', null, $this->_customdata->fileui_options);
  2226. break;
  2227. default :
  2228. break;
  2229. }
  2230. $mform->addElement('hidden', 'mailinfo_h', "0");
  2231. $mform->setType('mailinfo_h', PARAM_INT);
  2232. $mform->addElement('checkbox', 'mailinfo',get_string('enablenotification','assignment').
  2233. $OUTPUT->help_icon('enablenotification', 'assignment') .':' );
  2234. $mform->setType('mailinfo', PARAM_INT);
  2235. }
  2236. }
  2237. function add_action_buttons($cancel = true, $submitlabel = NULL) {
  2238. $mform =& $this->_form;
  2239. //if there are more to be graded.
  2240. if ($this->_customdata->nextid>0) {
  2241. $buttonarray=array();
  2242. $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
  2243. //@todo: fix accessibility: javascript dependency not necessary
  2244. $buttonarray[] = &$mform->createElement('submit', 'saveandnext', get_string('saveandnext'));
  2245. $buttonarray[] = &$mform->createElement('submit', 'next', get_string('next'));
  2246. $buttonarray[] = &$mform->createElement('cancel');
  2247. } else {
  2248. $buttonarray=array();
  2249. $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
  2250. $buttonarray[] = &$mform->createElement('cancel');
  2251. }
  2252. $mform->addGroup($buttonarray, 'grading_buttonar', '', array(' '), false);
  2253. $mform->closeHeaderBefore('grading_buttonar');
  2254. $mform->setType('grading_buttonar', PARAM_RAW);
  2255. }
  2256. function add_submission_content() {
  2257. $mform =& $this->_form;
  2258. $mform->addElement('header', 'Submission', get_string('submission', 'assignment'));
  2259. $mform->addElement('static', '', '' , $this->_customdata->submission_content );
  2260. }
  2261. protected function get_editor_options() {
  2262. $editoroptions = array();
  2263. $editoroptions['component'] = 'mod_assignment';
  2264. $editoroptions['filearea'] = 'feedback';
  2265. $editoroptions['noclean'] = false;
  2266. $editoroptions['maxfiles'] = 0; //TODO: no files for now, we need to first implement assignment_feedback area, integration with gradebook, files support in quickgrading, etc. (skodak)
  2267. $editoroptions['maxbytes'] = $this->_customdata->maxbytes;
  2268. $editoroptions['context'] = $this->_customdata->context;
  2269. return $editoroptions;
  2270. }
  2271. public function set_data($data) {
  2272. $editoroptions = $this->get_editor_options();
  2273. if (!isset($data->text)) {
  2274. $data->text = '';
  2275. }
  2276. if (!isset($data->format)) {
  2277. $data->textformat = FORMAT_HTML;
  2278. } else {
  2279. $data->textformat = $data->format;
  2280. }
  2281. if (!empty($this->_customdata->submission->id)) {
  2282. $itemid = $this->_customdata->submission->id;
  2283. } else {
  2284. $itemid = null;
  2285. }
  2286. switch ($this->_customdata->assignment->assignmenttype) {
  2287. case 'upload' :
  2288. case 'uploadsingle' :
  2289. $data = file_prepare_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
  2290. break;
  2291. default :
  2292. break;
  2293. }
  2294. $data = file_prepare_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
  2295. return parent::set_data($data);
  2296. }
  2297. public function get_data() {
  2298. $data = parent::get_data();
  2299. if (!empty($this->_customdata->submission->id)) {
  2300. $itemid = $this->_customdata->submission->id;
  2301. } else {
  2302. $itemid = null; //TODO: this is wrong, itemid MUST be known when saving files!! (skodak)
  2303. }
  2304. if ($data) {
  2305. $editoroptions = $this->get_editor_options();
  2306. switch ($this->_customdata->assignment->assignmenttype) {
  2307. case 'upload' :
  2308. case 'uploadsingle' :
  2309. $data = file_postupdate_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
  2310. break;
  2311. default :
  2312. break;
  2313. }
  2314. $data = file_postupdate_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
  2315. }
  2316. if ($this->use_advanced_grading() && !isset($data->advancedgrading)) {
  2317. $data->advancedgrading = null;
  2318. }
  2319. return $data;
  2320. }
  2321. }
  2322. /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
  2323. /**
  2324. * Deletes an assignment instance
  2325. *
  2326. * This is done by calling the delete_instance() method of the assignment type class
  2327. */
  2328. function assignment_delete_instance($id){
  2329. global $CFG, $DB;
  2330. if (! $assignment = $DB->get_record('assignment', array('id'=>$id))) {
  2331. return false;
  2332. }
  2333. // fall back to base class if plugin missing
  2334. $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
  2335. if (file_exists($classfile)) {
  2336. require_once($classfile);
  2337. $assignmentclass = "assignment_$assignment->assignmenttype";
  2338. } else {
  2339. debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
  2340. $assignmentclass = "assignment_base";
  2341. }
  2342. $ass = new $assignmentclass();
  2343. return $ass->delete_instance($assignment);
  2344. }
  2345. /**
  2346. * Updates an assignment instance
  2347. *
  2348. * This is done by calling the update_instance() method of the assignment type class
  2349. */
  2350. function assignment_update_instance($assignment){
  2351. global $CFG;
  2352. $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
  2353. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2354. $assignmentclass = "assignment_$assignment->assignmenttype";
  2355. $ass = new $assignmentclass();
  2356. return $ass->update_instance($assignment);
  2357. }
  2358. /**
  2359. * Adds an assignment instance
  2360. *
  2361. * This is done by calling the add_instance() method of the assignment type class
  2362. *
  2363. * @param stdClass $assignment
  2364. * @param mod_assignment_mod_form $mform
  2365. * @return int intance id
  2366. */
  2367. function assignment_add_instance($assignment, $mform = null) {
  2368. global $CFG;
  2369. $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
  2370. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2371. $assignmentclass = "assignment_$assignment->assignmenttype";
  2372. $ass = new $assignmentclass();
  2373. return $ass->add_instance($assignment);
  2374. }
  2375. /**
  2376. * Returns an outline of a user interaction with an assignment
  2377. *
  2378. * This is done by calling the user_outline() method of the assignment type class
  2379. */
  2380. function assignment_user_outline($course, $user, $mod, $assignment) {
  2381. global $CFG;
  2382. require_once("$CFG->libdir/gradelib.php");
  2383. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2384. $assignmentclass = "assignment_$assignment->assignmenttype";
  2385. $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
  2386. $grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
  2387. if (!empty($grades->items[0]->grades)) {
  2388. return $ass->user_outline(reset($grades->items[0]->grades));
  2389. } else {
  2390. return null;
  2391. }
  2392. }
  2393. /**
  2394. * Prints the complete info about a user's interaction with an assignment
  2395. *
  2396. * This is done by calling the user_complete() method of the assignment type class
  2397. */
  2398. function assignment_user_complete($course, $user, $mod, $assignment) {
  2399. global $CFG;
  2400. require_once("$CFG->libdir/gradelib.php");
  2401. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2402. $assignmentclass = "assignment_$assignment->assignmenttype";
  2403. $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
  2404. $grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
  2405. if (empty($grades->items[0]->grades)) {
  2406. $grade = false;
  2407. } else {
  2408. $grade = reset($grades->items[0]->grades);
  2409. }
  2410. return $ass->user_complete($user, $grade);
  2411. }
  2412. /**
  2413. * Function to be run periodically according to the moodle cron
  2414. *
  2415. * Finds all assignment notifications that have yet to be mailed out, and mails them
  2416. */
  2417. function assignment_cron () {
  2418. global $CFG, $USER, $DB;
  2419. /// first execute all crons in plugins
  2420. if ($plugins = core_component::get_plugin_list('assignment')) {
  2421. foreach ($plugins as $plugin=>$dir) {
  2422. require_once("$dir/assignment.class.php");
  2423. $assignmentclass = "assignment_$plugin";
  2424. $ass = new $assignmentclass();
  2425. $ass->cron();
  2426. }
  2427. }
  2428. /// Notices older than 1 day will not be mailed. This is to avoid the problem where
  2429. /// cron has not been running for a long time, and then suddenly people are flooded
  2430. /// with mail from the past few weeks or months
  2431. $timenow = time();
  2432. $endtime = $timenow - $CFG->maxeditingtime;
  2433. $starttime = $endtime - 24 * 3600; /// One day earlier
  2434. if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
  2435. $realuser = clone($USER);
  2436. foreach ($submissions as $key => $submission) {
  2437. $DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id));
  2438. }
  2439. $timenow = time();
  2440. foreach ($submissions as $submission) {
  2441. echo "Processing assignment submission $submission->id\n";
  2442. if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) {
  2443. echo "Could not find user $user->id\n";
  2444. continue;
  2445. }
  2446. if (! $course = $DB->get_record("course", array("id"=>$submission->course))) {
  2447. echo "Could not find course $submission->course\n";
  2448. continue;
  2449. }
  2450. /// Override the language and timezone of the "current" user, so that
  2451. /// mail is customised for the receiver.
  2452. cron_setup_user($user, $course);
  2453. $coursecontext = context_course::instance($submission->course);
  2454. $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
  2455. if (!is_enrolled($coursecontext, $user->id)) {
  2456. echo fullname($user)." not an active participant in " . $courseshortname . "\n";
  2457. continue;
  2458. }
  2459. if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) {
  2460. echo "Could not find teacher $submission->teacher\n";
  2461. continue;
  2462. }
  2463. if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
  2464. echo "Could not find course module for assignment id $submission->assignment\n";
  2465. continue;
  2466. }
  2467. if (! $mod->visible) { /// Hold mail notification for hidden assignments until later
  2468. continue;
  2469. }
  2470. $strassignments = get_string("modulenameplural", "assignment");
  2471. $strassignment = get_string("modulename", "assignment");
  2472. $assignmentinfo = new stdClass();
  2473. $assignmentinfo->teacher = fullname($teacher);
  2474. $assignmentinfo->assignment = format_string($submission->name,true);
  2475. $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
  2476. $postsubject = "$courseshortname: $strassignments: ".format_string($submission->name,true);
  2477. $posttext = "$courseshortname -> $strassignments -> ".format_string($submission->name,true)."\n";
  2478. $posttext .= "---------------------------------------------------------------------\n";
  2479. $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
  2480. $posttext .= "---------------------------------------------------------------------\n";
  2481. if ($user->mailformat == 1) { // HTML
  2482. $posthtml = "<p><font face=\"sans-serif\">".
  2483. "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$courseshortname</a> ->".
  2484. "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
  2485. "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
  2486. $posthtml .= "<hr /><font face=\"sans-serif\">";
  2487. $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
  2488. $posthtml .= "</font><hr />";
  2489. } else {
  2490. $posthtml = "";
  2491. }
  2492. $eventdata = new stdClass();
  2493. $eventdata->modulename = 'assignment';
  2494. $eventdata->userfrom = $teacher;
  2495. $eventdata->userto = $user;
  2496. $eventdata->subject = $postsubject;
  2497. $eventdata->fullmessage = $posttext;
  2498. $eventdata->fullmessageformat = FORMAT_PLAIN;
  2499. $eventdata->fullmessagehtml = $posthtml;
  2500. $eventdata->smallmessage = get_string('assignmentmailsmall', 'assignment', $assignmentinfo);
  2501. $eventdata->name = 'assignment_updates';
  2502. $eventdata->component = 'mod_assignment';
  2503. $eventdata->notification = 1;
  2504. $eventdata->contexturl = $assignmentinfo->url;
  2505. $eventdata->contexturlname = $assignmentinfo->assignment;
  2506. message_send($eventdata);
  2507. }
  2508. cron_setup_user();
  2509. }
  2510. return true;
  2511. }
  2512. /**
  2513. * Return grade for given user or all users.
  2514. *
  2515. * @param stdClass $assignment An assignment instance
  2516. * @param int $userid Optional user id, 0 means all users
  2517. * @return array An array of grades, false if none
  2518. */
  2519. function assignment_get_user_grades($assignment, $userid=0) {
  2520. global $CFG, $DB;
  2521. if ($userid) {
  2522. $user = "AND u.id = :userid";
  2523. $params = array('userid'=>$userid);
  2524. } else {
  2525. $user = "";
  2526. }
  2527. $params['aid'] = $assignment->id;
  2528. $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
  2529. s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
  2530. FROM {user} u, {assignment_submissions} s
  2531. WHERE u.id = s.userid AND s.assignment = :aid
  2532. $user";
  2533. return $DB->get_records_sql($sql, $params);
  2534. }
  2535. /**
  2536. * Update activity grades
  2537. *
  2538. * @category grade
  2539. * @param stdClass $assignment Assignment instance
  2540. * @param int $userid specific user only, 0 means all
  2541. * @param bool $nullifnone Not used
  2542. */
  2543. function assignment_update_grades($assignment, $userid=0, $nullifnone=true) {
  2544. global $CFG, $DB;
  2545. require_once($CFG->libdir.'/gradelib.php');
  2546. if ($assignment->grade == 0) {
  2547. assignment_grade_item_update($assignment);
  2548. } else if ($grades = assignment_get_user_grades($assignment, $userid)) {
  2549. foreach($grades as $k=>$v) {
  2550. if ($v->rawgrade == -1) {
  2551. $grades[$k]->rawgrade = null;
  2552. }
  2553. }
  2554. assignment_grade_item_update($assignment, $grades);
  2555. } else {
  2556. assignment_grade_item_update($assignment);
  2557. }
  2558. }
  2559. /**
  2560. * Update all grades in gradebook.
  2561. */
  2562. function assignment_upgrade_grades() {
  2563. global $DB;
  2564. $sql = "SELECT COUNT('x')
  2565. FROM {assignment} a, {course_modules} cm, {modules} m
  2566. WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
  2567. $count = $DB->count_records_sql($sql);
  2568. $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
  2569. FROM {assignment} a, {course_modules} cm, {modules} m
  2570. WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
  2571. $rs = $DB->get_recordset_sql($sql);
  2572. if ($rs->valid()) {
  2573. // too much debug output
  2574. $pbar = new progress_bar('assignmentupgradegrades', 500, true);
  2575. $i=0;
  2576. foreach ($rs as $assignment) {
  2577. $i++;
  2578. upgrade_set_timeout(60*5); // set up timeout, may also abort execution
  2579. assignment_update_grades($assignment);
  2580. $pbar->update($i, $count, "Updating Assignment grades ($i/$count).");
  2581. }
  2582. upgrade_set_timeout(); // reset to default timeout
  2583. }
  2584. $rs->close();
  2585. }
  2586. /**
  2587. * Create grade item for given assignment
  2588. *
  2589. * @category grade
  2590. * @param stdClass $assignment An assignment instance with extra cmidnumber property
  2591. * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
  2592. * @return int 0 if ok, error code otherwise
  2593. */
  2594. function assignment_grade_item_update($assignment, $grades=NULL) {
  2595. global $CFG;
  2596. require_once($CFG->libdir.'/gradelib.php');
  2597. if (!isset($assignment->courseid)) {
  2598. $assignment->courseid = $assignment->course;
  2599. }
  2600. $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
  2601. if ($assignment->grade > 0) {
  2602. $params['gradetype'] = GRADE_TYPE_VALUE;
  2603. $params['grademax'] = $assignment->grade;
  2604. $params['grademin'] = 0;
  2605. } else if ($assignment->grade < 0) {
  2606. $params['gradetype'] = GRADE_TYPE_SCALE;
  2607. $params['scaleid'] = -$assignment->grade;
  2608. } else {
  2609. $params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only
  2610. }
  2611. if ($grades === 'reset') {
  2612. $params['reset'] = true;
  2613. $grades = NULL;
  2614. }
  2615. return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
  2616. }
  2617. /**
  2618. * Delete grade item for given assignment
  2619. *
  2620. * @category grade
  2621. * @param object $assignment object
  2622. * @return object assignment
  2623. */
  2624. function assignment_grade_item_delete($assignment) {
  2625. global $CFG;
  2626. require_once($CFG->libdir.'/gradelib.php');
  2627. if (!isset($assignment->courseid)) {
  2628. $assignment->courseid = $assignment->course;
  2629. }
  2630. return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
  2631. }
  2632. /**
  2633. * Serves assignment submissions and other files.
  2634. *
  2635. * @package mod_assignment
  2636. * @category files
  2637. * @param stdClass $course course object
  2638. * @param stdClass $cm course module object
  2639. * @param stdClass $context context object
  2640. * @param string $filearea file area
  2641. * @param array $args extra arguments
  2642. * @param bool $forcedownload whether or not force download
  2643. * @param array $options additional options affecting the file serving
  2644. * @return bool false if file not found, does not return if found - just send the file
  2645. */
  2646. function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
  2647. global $CFG, $DB;
  2648. if ($context->contextlevel != CONTEXT_MODULE) {
  2649. return false;
  2650. }
  2651. require_login($course, false, $cm);
  2652. if (!$assignment = $DB->get_record('assignment', array('id'=>$cm->instance))) {
  2653. return false;
  2654. }
  2655. require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
  2656. $assignmentclass = 'assignment_'.$assignment->assignmenttype;
  2657. $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
  2658. return $assignmentinstance->send_file($filearea, $args, $forcedownload, $options);
  2659. }
  2660. /**
  2661. * Checks if a scale is being used by an assignment
  2662. *
  2663. * This is used by the backup code to decide whether to back up a scale
  2664. * @param $assignmentid int
  2665. * @param $scaleid int
  2666. * @return boolean True if the scale is used by the assignment
  2667. */
  2668. function assignment_scale_used($assignmentid, $scaleid) {
  2669. global $DB;
  2670. $return = false;
  2671. $rec = $DB->get_record('assignment', array('id'=>$assignmentid,'grade'=>-$scaleid));
  2672. if (!empty($rec) && !empty($scaleid)) {
  2673. $return = true;
  2674. }
  2675. return $return;
  2676. }
  2677. /**
  2678. * Checks if scale is being used by any instance of assignment
  2679. *
  2680. * This is used to find out if scale used anywhere
  2681. * @param $scaleid int
  2682. * @return boolean True if the scale is used by any assignment
  2683. */
  2684. function assignment_scale_used_anywhere($scaleid) {
  2685. global $DB;
  2686. if ($scaleid and $DB->record_exists('assignment', array('grade'=>-$scaleid))) {
  2687. return true;
  2688. } else {
  2689. return false;
  2690. }
  2691. }
  2692. /**
  2693. * Make sure up-to-date events are created for all assignment instances
  2694. *
  2695. * This standard function will check all instances of this module
  2696. * and make sure there are up-to-date events created for each of them.
  2697. * If courseid = 0, then every assignment event in the site is checked, else
  2698. * only assignment events belonging to the course specified are checked.
  2699. * This function is used, in its new format, by restore_refresh_events()
  2700. *
  2701. * @param $courseid int optional If zero then all assignments for all courses are covered
  2702. * @return boolean Always returns true
  2703. */
  2704. function assignment_refresh_events($courseid = 0) {
  2705. global $DB, $CFG;
  2706. require_once($CFG->dirroot.'/calendar/lib.php');
  2707. if ($courseid == 0) {
  2708. if (! $assignments = $DB->get_records("assignment")) {
  2709. return true;
  2710. }
  2711. } else {
  2712. if (! $assignments = $DB->get_records("assignment", array("course"=>$courseid))) {
  2713. return true;
  2714. }
  2715. }
  2716. $moduleid = $DB->get_field('modules', 'id', array('name'=>'assignment'));
  2717. foreach ($assignments as $assignment) {
  2718. $cm = get_coursemodule_from_instance('assignment', $assignment->id, $courseid, false, MUST_EXIST);
  2719. $event = new stdClass();
  2720. $event->name = $assignment->name;
  2721. $event->description = format_module_intro('assignment', $assignment, $cm->id);
  2722. $event->timestart = $assignment->timedue;
  2723. if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
  2724. $calendarevent = calendar_event::load($event->id);
  2725. $calendarevent->update($event);
  2726. } else {
  2727. $event->courseid = $assignment->course;
  2728. $event->groupid = 0;
  2729. $event->userid = 0;
  2730. $event->modulename = 'assignment';
  2731. $event->instance = $assignment->id;
  2732. $event->eventtype = 'due';
  2733. $event->timeduration = 0;
  2734. $event->visible = $DB->get_field('course_modules', 'visible', array('module'=>$moduleid, 'instance'=>$assignment->id));
  2735. calendar_event::create($event);
  2736. }
  2737. }
  2738. return true;
  2739. }
  2740. /**
  2741. * Print recent activity from all assignments in a given course
  2742. *
  2743. * This is used by the recent activity block
  2744. */
  2745. function assignment_print_recent_activity($course, $viewfullnames, $timestart) {
  2746. global $CFG, $USER, $DB, $OUTPUT;
  2747. // do not use log table if possible, it may be huge
  2748. if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, asb.userid,
  2749. u.firstname, u.lastname, u.email, u.picture
  2750. FROM {assignment_submissions} asb
  2751. JOIN {assignment} a ON a.id = asb.assignment
  2752. JOIN {course_modules} cm ON cm.instance = a.id
  2753. JOIN {modules} md ON md.id = cm.module
  2754. JOIN {user} u ON u.id = asb.userid
  2755. WHERE asb.timemodified > ? AND
  2756. a.course = ? AND
  2757. md.name = 'assignment'
  2758. ORDER BY asb.timemodified ASC", array($timestart, $course->id))) {
  2759. return false;
  2760. }
  2761. $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
  2762. $show = array();
  2763. $grader = array();
  2764. foreach($submissions as $submission) {
  2765. if (!array_key_exists($submission->cmid, $modinfo->cms)) {
  2766. continue;
  2767. }
  2768. $cm = $modinfo->cms[$submission->cmid];
  2769. if (!$cm->uservisible) {
  2770. continue;
  2771. }
  2772. if ($submission->userid == $USER->id) {
  2773. $show[] = $submission;
  2774. continue;
  2775. }
  2776. // the act of sumbitting of assignment may be considered private - only graders will see it if specified
  2777. if (empty($CFG->assignment_showrecentsubmissions)) {
  2778. if (!array_key_exists($cm->id, $grader)) {
  2779. $grader[$cm->id] = has_capability('moodle/grade:viewall', context_module::instance($cm->id));
  2780. }
  2781. if (!$grader[$cm->id]) {
  2782. continue;
  2783. }
  2784. }
  2785. $groupmode = groups_get_activity_groupmode($cm, $course);
  2786. if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
  2787. if (isguestuser()) {
  2788. // shortcut - guest user does not belong into any group
  2789. continue;
  2790. }
  2791. if (is_null($modinfo->groups)) {
  2792. $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
  2793. }
  2794. // this will be slow - show only users that share group with me in this cm
  2795. if (empty($modinfo->groups[$cm->id])) {
  2796. continue;
  2797. }
  2798. $usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
  2799. if (is_array($usersgroups)) {
  2800. $usersgroups = array_keys($usersgroups);
  2801. $intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
  2802. if (empty($intersect)) {
  2803. continue;
  2804. }
  2805. }
  2806. }
  2807. $show[] = $submission;
  2808. }
  2809. if (empty($show)) {
  2810. return false;
  2811. }
  2812. echo $OUTPUT->heading(get_string('newsubmissions', 'assignment').':', 3);
  2813. foreach ($show as $submission) {
  2814. $cm = $modinfo->cms[$submission->cmid];
  2815. $link = $CFG->wwwroot.'/mod/assignment/view.php?id='.$cm->id;
  2816. print_recent_activity_note($submission->timemodified, $submission, $cm->name, $link, false, $viewfullnames);
  2817. }
  2818. return true;
  2819. }
  2820. /**
  2821. * Returns all assignments since a given time in specified forum.
  2822. */
  2823. function assignment_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
  2824. global $CFG, $COURSE, $USER, $DB;
  2825. if ($COURSE->id == $courseid) {
  2826. $course = $COURSE;
  2827. } else {
  2828. $course = $DB->get_record('course', array('id'=>$courseid));
  2829. }
  2830. $modinfo = get_fast_modinfo($course);
  2831. $cm = $modinfo->cms[$cmid];
  2832. $params = array();
  2833. if ($userid) {
  2834. $userselect = "AND u.id = :userid";
  2835. $params['userid'] = $userid;
  2836. } else {
  2837. $userselect = "";
  2838. }
  2839. if ($groupid) {
  2840. $groupselect = "AND gm.groupid = :groupid";
  2841. $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
  2842. $params['groupid'] = $groupid;
  2843. } else {
  2844. $groupselect = "";
  2845. $groupjoin = "";
  2846. }
  2847. $params['cminstance'] = $cm->instance;
  2848. $params['timestart'] = $timestart;
  2849. $userfields = user_picture::fields('u', null, 'userid');
  2850. if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified,
  2851. $userfields
  2852. FROM {assignment_submissions} asb
  2853. JOIN {assignment} a ON a.id = asb.assignment
  2854. JOIN {user} u ON u.id = asb.userid
  2855. $groupjoin
  2856. WHERE asb.timemodified > :timestart AND a.id = :cminstance
  2857. $userselect $groupselect
  2858. ORDER BY asb.timemodified ASC", $params)) {
  2859. return;
  2860. }
  2861. $groupmode = groups_get_activity_groupmode($cm, $course);
  2862. $cm_context = context_module::instance($cm->id);
  2863. $grader = has_capability('moodle/grade:viewall', $cm_context);
  2864. $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
  2865. $viewfullnames = has_capability('moodle/site:viewfullnames', $cm_context);
  2866. if (is_null($modinfo->groups)) {
  2867. $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
  2868. }
  2869. $show = array();
  2870. foreach($submissions as $submission) {
  2871. if ($submission->userid == $USER->id) {
  2872. $show[] = $submission;
  2873. continue;
  2874. }
  2875. // the act of submitting of assignment may be considered private - only graders will see it if specified
  2876. if (empty($CFG->assignment_showrecentsubmissions)) {
  2877. if (!$grader) {
  2878. continue;
  2879. }
  2880. }
  2881. if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
  2882. if (isguestuser()) {
  2883. // shortcut - guest user does not belong into any group
  2884. continue;
  2885. }
  2886. // this will be slow - show only users that share group with me in this cm
  2887. if (empty($modinfo->groups[$cm->id])) {
  2888. continue;
  2889. }
  2890. $usersgroups = groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
  2891. if (is_array($usersgroups)) {
  2892. $usersgroups = array_keys($usersgroups);
  2893. $intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
  2894. if (empty($intersect)) {
  2895. continue;
  2896. }
  2897. }
  2898. }
  2899. $show[] = $submission;
  2900. }
  2901. if (empty($show)) {
  2902. return;
  2903. }
  2904. if ($grader) {
  2905. require_once($CFG->libdir.'/gradelib.php');
  2906. $userids = array();
  2907. foreach ($show as $id=>$submission) {
  2908. $userids[] = $submission->userid;
  2909. }
  2910. $grades = grade_get_grades($courseid, 'mod', 'assignment', $cm->instance, $userids);
  2911. }
  2912. $aname = format_string($cm->name,true);
  2913. foreach ($show as $submission) {
  2914. $tmpactivity = new stdClass();
  2915. $tmpactivity->type = 'assignment';
  2916. $tmpactivity->cmid = $cm->id;
  2917. $tmpactivity->name = $aname;
  2918. $tmpactivity->sectionnum = $cm->sectionnum;
  2919. $tmpactivity->timestamp = $submission->timemodified;
  2920. if ($grader) {
  2921. $tmpactivity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
  2922. }
  2923. $userfields = explode(',', user_picture::fields());
  2924. foreach ($userfields as $userfield) {
  2925. if ($userfield == 'id') {
  2926. $tmpactivity->user->{$userfield} = $submission->userid; // aliased in SQL above
  2927. } else {
  2928. $tmpactivity->user->{$userfield} = $submission->{$userfield};
  2929. }
  2930. }
  2931. $tmpactivity->user->fullname = fullname($submission, $viewfullnames);
  2932. $activities[$index++] = $tmpactivity;
  2933. }
  2934. return;
  2935. }
  2936. /**
  2937. * Print recent activity from all assignments in a given course
  2938. *
  2939. * This is used by course/recent.php
  2940. */
  2941. function assignment_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
  2942. global $CFG, $OUTPUT;
  2943. echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
  2944. echo "<tr><td class=\"userpicture\" valign=\"top\">";
  2945. echo $OUTPUT->user_picture($activity->user);
  2946. echo "</td><td>";
  2947. if ($detail) {
  2948. $modname = $modnames[$activity->type];
  2949. echo '<div class="title">';
  2950. echo "<img src=\"" . $OUTPUT->pix_url('icon', 'assignment') . "\" ".
  2951. "class=\"icon\" alt=\"$modname\">";
  2952. echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id={$activity->cmid}\">{$activity->name}</a>";
  2953. echo '</div>';
  2954. }
  2955. if (isset($activity->grade)) {
  2956. echo '<div class="grade">';
  2957. echo get_string('grade').': ';
  2958. echo $activity->grade;
  2959. echo '</div>';
  2960. }
  2961. echo '<div class="user">';
  2962. echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
  2963. ."{$activity->user->fullname}</a> - ".userdate($activity->timestamp);
  2964. echo '</div>';
  2965. echo "</td></tr></table>";
  2966. }
  2967. /// GENERIC SQL FUNCTIONS
  2968. /**
  2969. * Fetch info from logs
  2970. *
  2971. * @param $log object with properties ->info (the assignment id) and ->userid
  2972. * @return array with assignment name and user firstname and lastname
  2973. */
  2974. function assignment_log_info($log) {
  2975. global $CFG, $DB;
  2976. return $DB->get_record_sql("SELECT a.name, u.firstname, u.lastname
  2977. FROM {assignment} a, {user} u
  2978. WHERE a.id = ? AND u.id = ?", array($log->info, $log->userid));
  2979. }
  2980. /**
  2981. * Return list of marked submissions that have not been mailed out for currently enrolled students
  2982. *
  2983. * @return array
  2984. */
  2985. function assignment_get_unmailed_submissions($starttime, $endtime) {
  2986. global $CFG, $DB;
  2987. return $DB->get_records_sql("SELECT s.*, a.course, a.name
  2988. FROM {assignment_submissions} s,
  2989. {assignment} a
  2990. WHERE s.mailed = 0
  2991. AND s.timemarked <= ?
  2992. AND s.timemarked >= ?
  2993. AND s.assignment = a.id", array($endtime, $starttime));
  2994. }
  2995. /**
  2996. * Counts all complete (real) assignment submissions by enrolled students for the given course modeule.
  2997. *
  2998. * @deprecated Since Moodle 2.2 MDL-abc - Please do not use this function any more.
  2999. * @param cm_info $cm The course module that we wish to perform the count on.
  3000. * @param int $groupid (optional) If nonzero then count is restricted to this group
  3001. * @return int The number of submissions
  3002. */
  3003. function assignment_count_real_submissions($cm, $groupid=0) {
  3004. global $CFG, $DB;
  3005. // Grab the assignment type for the given course module
  3006. $assignmenttype = $DB->get_field($cm->modname, 'assignmenttype', array('id' => $cm->instance), MUST_EXIST);
  3007. // Create the expected class file path and class name for the returned assignemnt type
  3008. $filename = "{$CFG->dirroot}/mod/assignment/type/{$assignmenttype}/assignment.class.php";
  3009. $classname = "assignment_{$assignmenttype}";
  3010. // If the file exists and the class is not already loaded we require the class file
  3011. if (file_exists($filename) && !class_exists($classname)) {
  3012. require_once($filename);
  3013. }
  3014. // If the required class is still not loaded then we revert to assignment base
  3015. if (!class_exists($classname)) {
  3016. $classname = 'assignment_base';
  3017. }
  3018. $instance = new $classname;
  3019. // Attach the course module to the assignment type instance and then call the method for counting submissions
  3020. $instance->cm = $cm;
  3021. return $instance->count_real_submissions($groupid);
  3022. }
  3023. /**
  3024. * Return all assignment submissions by ENROLLED students (even empty)
  3025. *
  3026. * There are also assignment type methods get_submissions() wich in the default
  3027. * implementation simply call this function.
  3028. * @param $sort string optional field names for the ORDER BY in the sql query
  3029. * @param $dir string optional specifying the sort direction, defaults to DESC
  3030. * @return array The submission objects indexed by id
  3031. */
  3032. function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
  3033. /// Return all assignment submissions by ENROLLED students (even empty)
  3034. global $CFG, $DB;
  3035. if ($sort == "lastname" or $sort == "firstname") {
  3036. $sort = "u.$sort $dir";
  3037. } else if (empty($sort)) {
  3038. $sort = "a.timemodified DESC";
  3039. } else {
  3040. $sort = "a.$sort $dir";
  3041. }
  3042. /* not sure this is needed at all since assignment already has a course define, so this join?
  3043. $select = "s.course = '$assignment->course' AND";
  3044. if ($assignment->course == SITEID) {
  3045. $select = '';
  3046. }*/
  3047. $cm = get_coursemodule_from_instance('assignment', $assignment->id, $assignment->course);
  3048. $context = context_module::instance($cm->id);
  3049. list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:submit');
  3050. $params['assignmentid'] = $assignment->id;
  3051. return $DB->get_records_sql("SELECT a.*
  3052. FROM {assignment_submissions} a
  3053. INNER JOIN (". $enroledsql .") u ON u.id = a.userid
  3054. WHERE u.id = a.userid
  3055. AND a.assignment = :assignmentid
  3056. ORDER BY $sort", $params);
  3057. }
  3058. /**
  3059. * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
  3060. * for the course (see resource).
  3061. *
  3062. * Given a course_module object, this function returns any "extra" information that may be needed
  3063. * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
  3064. *
  3065. * @param stdClass $coursemodule object The coursemodule object (record).
  3066. * @return cached_cm_info An object on information that the courses will know about (most noticeably, an icon).
  3067. */
  3068. function assignment_get_coursemodule_info($coursemodule) {
  3069. global $CFG, $DB;
  3070. if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance),
  3071. 'id, assignmenttype, name, intro, introformat')) {
  3072. return false;
  3073. }
  3074. $libfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
  3075. if (file_exists($libfile)) {
  3076. require_once($libfile);
  3077. $assignmentclass = "assignment_$assignment->assignmenttype";
  3078. $ass = new $assignmentclass('staticonly');
  3079. if (!($result = $ass->get_coursemodule_info($coursemodule))) {
  3080. $result = new cached_cm_info();
  3081. $result->name = $assignment->name;
  3082. }
  3083. if ($coursemodule->showdescription) {
  3084. // Convert intro to html. Do not filter cached version, filters run at display time.
  3085. $result->content = format_module_intro('assignment', $assignment, $coursemodule->id, false);
  3086. }
  3087. return $result;
  3088. } else {
  3089. debugging('Incorrect assignment type: '.$assignment->assignmenttype);
  3090. return false;
  3091. }
  3092. }
  3093. /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
  3094. /**
  3095. * Returns an array of installed assignment types indexed and sorted by name
  3096. *
  3097. * @return array The index is the name of the assignment type, the value its full name from the language strings
  3098. */
  3099. function assignment_types() {
  3100. $types = array();
  3101. $names = core_component::get_plugin_list('assignment');
  3102. foreach ($names as $name=>$dir) {
  3103. $types[$name] = get_string('type'.$name, 'assignment');
  3104. // ugly hack to support pluggable assignment type titles..
  3105. if ($types[$name] == '[[type'.$name.']]') {
  3106. $types[$name] = get_string('type'.$name, 'assignment_'.$name);
  3107. }
  3108. }
  3109. asort($types);
  3110. return $types;
  3111. }
  3112. function assignment_print_overview($courses, &$htmlarray) {
  3113. global $USER, $CFG, $DB;
  3114. require_once($CFG->libdir.'/gradelib.php');
  3115. if (empty($courses) || !is_array($courses) || count($courses) == 0) {
  3116. return array();
  3117. }
  3118. if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
  3119. return;
  3120. }
  3121. $assignmentids = array();
  3122. // Do assignment_base::isopen() here without loading the whole thing for speed
  3123. foreach ($assignments as $key => $assignment) {
  3124. $time = time();
  3125. if ($assignment->timedue) {
  3126. if ($assignment->preventlate) {
  3127. $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
  3128. } else {
  3129. $isopen = ($assignment->timeavailable <= $time);
  3130. }
  3131. }
  3132. if (empty($isopen) || empty($assignment->timedue)) {
  3133. unset($assignments[$key]);
  3134. } else {
  3135. $assignmentids[] = $assignment->id;
  3136. }
  3137. }
  3138. if (empty($assignmentids)){
  3139. // no assignments to look at - we're done
  3140. return true;
  3141. }
  3142. $strduedate = get_string('duedate', 'assignment');
  3143. $strduedateno = get_string('duedateno', 'assignment');
  3144. $strgraded = get_string('graded', 'assignment');
  3145. $strnotgradedyet = get_string('notgradedyet', 'assignment');
  3146. $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
  3147. $strsubmitted = get_string('submitted', 'assignment');
  3148. $strassignment = get_string('modulename', 'assignment');
  3149. $strreviewed = get_string('reviewed','assignment');
  3150. // NOTE: we do all possible database work here *outside* of the loop to ensure this scales
  3151. //
  3152. list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
  3153. // build up and array of unmarked submissions indexed by assignment id/ userid
  3154. // for use where the user has grading rights on assignment
  3155. $rs = $DB->get_recordset_sql("SELECT id, assignment, userid
  3156. FROM {assignment_submissions}
  3157. WHERE teacher = 0 AND timemarked = 0
  3158. AND assignment $sqlassignmentids", $assignmentidparams);
  3159. $unmarkedsubmissions = array();
  3160. foreach ($rs as $rd) {
  3161. $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
  3162. }
  3163. $rs->close();
  3164. // get all user submissions, indexed by assignment id
  3165. $mysubmissions = $DB->get_records_sql("SELECT assignment, timemarked, teacher, grade
  3166. FROM {assignment_submissions}
  3167. WHERE userid = ? AND
  3168. assignment $sqlassignmentids", array_merge(array($USER->id), $assignmentidparams));
  3169. foreach ($assignments as $assignment) {
  3170. $grading_info = grade_get_grades($assignment->course, 'mod', 'assignment', $assignment->id, $USER->id);
  3171. $final_grade = $grading_info->items[0]->grades[$USER->id];
  3172. $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
  3173. '<a '.($assignment->visible ? '':' class="dimmed"').
  3174. 'title="'.$strassignment.'" href="'.$CFG->wwwroot.
  3175. '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
  3176. $assignment->name.'</a></div>';
  3177. if ($assignment->timedue) {
  3178. $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
  3179. } else {
  3180. $str .= '<div class="info">'.$strduedateno.'</div>';
  3181. }
  3182. $context = context_module::instance($assignment->coursemodule);
  3183. if (has_capability('mod/assignment:grade', $context)) {
  3184. // count how many people can submit
  3185. $submissions = 0; // init
  3186. if ($students = get_enrolled_users($context, 'mod/assignment:view', 0, 'u.id')) {
  3187. foreach ($students as $student) {
  3188. if (isset($unmarkedsubmissions[$assignment->id][$student->id])) {
  3189. $submissions++;
  3190. }
  3191. }
  3192. }
  3193. if ($submissions) {
  3194. $link = new moodle_url('/mod/assignment/submissions.php', array('id'=>$assignment->coursemodule));
  3195. $str .= '<div class="details"><a href="'.$link.'">'.get_string('submissionsnotgraded', 'assignment', $submissions).'</a></div>';
  3196. }
  3197. } else {
  3198. $str .= '<div class="details">';
  3199. if (isset($mysubmissions[$assignment->id])) {
  3200. $submission = $mysubmissions[$assignment->id];
  3201. if ($submission->teacher == 0 && $submission->timemarked == 0 && !$final_grade->grade) {
  3202. $str .= $strsubmitted . ', ' . $strnotgradedyet;
  3203. } else if ($submission->grade <= 0 && !$final_grade->grade) {
  3204. $str .= $strsubmitted . ', ' . $strreviewed;
  3205. } else {
  3206. $str .= $strsubmitted . ', ' . $strgraded;
  3207. }
  3208. } else {
  3209. $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
  3210. }
  3211. $str .= '</div>';
  3212. }
  3213. $str .= '</div>';
  3214. if (empty($htmlarray[$assignment->course]['assignment'])) {
  3215. $htmlarray[$assignment->course]['assignment'] = $str;
  3216. } else {
  3217. $htmlarray[$assignment->course]['assignment'] .= $str;
  3218. }
  3219. }
  3220. }
  3221. function assignment_display_lateness($timesubmitted, $timedue) {
  3222. if (!$timedue) {
  3223. return '';
  3224. }
  3225. $time = $timedue - $timesubmitted;
  3226. if ($time < 0) {
  3227. $timetext = get_string('late', 'assignment', format_time($time));
  3228. return ' (<span class="late">'.$timetext.'</span>)';
  3229. } else {
  3230. $timetext = get_string('early', 'assignment', format_time($time));
  3231. return ' (<span class="early">'.$timetext.'</span>)';
  3232. }
  3233. }
  3234. function assignment_get_view_actions() {
  3235. return array('view');
  3236. }
  3237. function assignment_get_post_actions() {
  3238. return array('upload');
  3239. }
  3240. function assignment_get_types() {
  3241. global $CFG;
  3242. $types = array();
  3243. $type = new stdClass();
  3244. $type->modclass = MOD_CLASS_ACTIVITY;
  3245. $type->type = "assignment_group_start";
  3246. $type->typestr = '--'.get_string('modulenameplural', 'assignment');
  3247. $types[] = $type;
  3248. $standardassignments = array('upload','online','uploadsingle','offline');
  3249. foreach ($standardassignments as $assignmenttype) {
  3250. $type = new stdClass();
  3251. $type->modclass = MOD_CLASS_ACTIVITY;
  3252. $type->type = "assignment&amp;type=$assignmenttype";
  3253. $type->typestr = get_string("type$assignmenttype", 'assignment');
  3254. $types[] = $type;
  3255. }
  3256. /// Drop-in extra assignment types
  3257. $assignmenttypes = core_component::get_plugin_list('assignment');
  3258. foreach ($assignmenttypes as $assignmenttype=>$fulldir) {
  3259. if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) { // Not wanted
  3260. continue;
  3261. }
  3262. if (!in_array($assignmenttype, $standardassignments)) {
  3263. $type = new stdClass();
  3264. $type->modclass = MOD_CLASS_ACTIVITY;
  3265. $type->type = "assignment&amp;type=$assignmenttype";
  3266. $type->typestr = get_string("type$assignmenttype", 'assignment_'.$assignmenttype);
  3267. $types[] = $type;
  3268. }
  3269. }
  3270. $type = new stdClass();
  3271. $type->modclass = MOD_CLASS_ACTIVITY;
  3272. $type->type = "assignment_group_end";
  3273. $type->typestr = '--';
  3274. $types[] = $type;
  3275. return $types;
  3276. }
  3277. /**
  3278. * Removes all grades from gradebook
  3279. *
  3280. * @param int $courseid The ID of the course to reset
  3281. * @param string $type Optional type of assignment to limit the reset to a particular assignment type
  3282. */
  3283. function assignment_reset_gradebook($courseid, $type='') {
  3284. global $CFG, $DB;
  3285. $params = array('courseid'=>$courseid);
  3286. if ($type) {
  3287. $type = "AND a.assignmenttype= :type";
  3288. $params['type'] = $type;
  3289. }
  3290. $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
  3291. FROM {assignment} a, {course_modules} cm, {modules} m
  3292. WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid $type";
  3293. if ($assignments = $DB->get_records_sql($sql, $params)) {
  3294. foreach ($assignments as $assignment) {
  3295. assignment_grade_item_update($assignment, 'reset');
  3296. }
  3297. }
  3298. }
  3299. /**
  3300. * This function is used by the reset_course_userdata function in moodlelib.
  3301. * This function will remove all posts from the specified assignment
  3302. * and clean up any related data.
  3303. * @param $data the data submitted from the reset course.
  3304. * @return array status array
  3305. */
  3306. function assignment_reset_userdata($data) {
  3307. global $CFG;
  3308. $status = array();
  3309. foreach (core_component::get_plugin_list('assignment') as $type=>$dir) {
  3310. require_once("$dir/assignment.class.php");
  3311. $assignmentclass = "assignment_$type";
  3312. $ass = new $assignmentclass();
  3313. $status = array_merge($status, $ass->reset_userdata($data));
  3314. }
  3315. // Updating dates - shift may be negative too.
  3316. if ($data->timeshift) {
  3317. shift_course_mod_dates('assignment',
  3318. array('timedue', 'timeavailable'),
  3319. $data->timeshift,
  3320. $data->courseid);
  3321. $status[] = array('component' => get_string('modulenameplural', 'assignment'),
  3322. 'item' => get_string('datechanged'),
  3323. 'error' => false);
  3324. }
  3325. return $status;
  3326. }
  3327. /**
  3328. * Implementation of the function for printing the form elements that control
  3329. * whether the course reset functionality affects the assignment.
  3330. * @param $mform form passed by reference
  3331. */
  3332. function assignment_reset_course_form_definition(&$mform) {
  3333. $mform->addElement('header', 'assignmentheader', get_string('modulenameplural', 'assignment'));
  3334. $mform->addElement('advcheckbox', 'reset_assignment_submissions', get_string('deleteallsubmissions','assignment'));
  3335. }
  3336. /**
  3337. * Course reset form defaults.
  3338. */
  3339. function assignment_reset_course_form_defaults($course) {
  3340. return array('reset_assignment_submissions'=>1);
  3341. }
  3342. /**
  3343. * Returns all other caps used in module
  3344. */
  3345. function assignment_get_extra_capabilities() {
  3346. return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/grade:managegradingforms');
  3347. }
  3348. /**
  3349. * @param string $feature FEATURE_xx constant for requested feature
  3350. * @return mixed True if module supports feature, null if doesn't know
  3351. */
  3352. function assignment_supports($feature) {
  3353. switch($feature) {
  3354. case FEATURE_GROUPS: return true;
  3355. case FEATURE_GROUPINGS: return true;
  3356. case FEATURE_GROUPMEMBERSONLY: return true;
  3357. case FEATURE_MOD_INTRO: return true;
  3358. case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
  3359. case FEATURE_GRADE_HAS_GRADE: return true;
  3360. case FEATURE_GRADE_OUTCOMES: return true;
  3361. case FEATURE_GRADE_HAS_GRADE: return true;
  3362. case FEATURE_BACKUP_MOODLE2: return true;
  3363. case FEATURE_SHOW_DESCRIPTION: return true;
  3364. case FEATURE_ADVANCED_GRADING: return true;
  3365. case FEATURE_PLAGIARISM: return true;
  3366. default: return null;
  3367. }
  3368. }
  3369. /**
  3370. * Adds module specific settings to the settings block
  3371. *
  3372. * @param settings_navigation $settings The settings navigation object
  3373. * @param navigation_node $assignmentnode The node to add module settings to
  3374. */
  3375. function assignment_extend_settings_navigation(settings_navigation $settings, navigation_node $assignmentnode) {
  3376. global $PAGE, $DB, $USER, $CFG;
  3377. $assignmentrow = $DB->get_record("assignment", array("id" => $PAGE->cm->instance));
  3378. $classfile = "$CFG->dirroot/mod/assignment/type/$assignmentrow->assignmenttype/assignment.class.php";
  3379. if (!file_exists($classfile)) {
  3380. return;
  3381. }
  3382. require_once($classfile);
  3383. $assignmentclass = 'assignment_'.$assignmentrow->assignmenttype;
  3384. $assignmentinstance = new $assignmentclass($PAGE->cm->id, $assignmentrow, $PAGE->cm, $PAGE->course);
  3385. $allgroups = false;
  3386. // Add assignment submission information
  3387. if (has_capability('mod/assignment:grade', $PAGE->cm->context)) {
  3388. if ($allgroups && has_capability('moodle/site:accessallgroups', $PAGE->cm->context)) {
  3389. $group = 0;
  3390. } else {
  3391. $group = groups_get_activity_group($PAGE->cm);
  3392. }
  3393. $link = new moodle_url('/mod/assignment/submissions.php', array('id'=>$PAGE->cm->id));
  3394. if ($assignmentrow->assignmenttype == 'offline') {
  3395. $string = get_string('viewfeedback', 'assignment');
  3396. } else if ($count = $assignmentinstance->count_real_submissions($group)) {
  3397. $string = get_string('viewsubmissions', 'assignment', $count);
  3398. } else {
  3399. $string = get_string('noattempts', 'assignment');
  3400. }
  3401. $assignmentnode->add($string, $link, navigation_node::TYPE_SETTING);
  3402. }
  3403. if (is_object($assignmentinstance) && method_exists($assignmentinstance, 'extend_settings_navigation')) {
  3404. $assignmentinstance->extend_settings_navigation($assignmentnode);
  3405. }
  3406. }
  3407. /**
  3408. * generate zip file from array of given files
  3409. * @param array $filesforzipping - array of files to pass into archive_to_pathname
  3410. * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
  3411. */
  3412. function assignment_pack_files($filesforzipping) {
  3413. global $CFG;
  3414. //create path for new zip file.
  3415. $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
  3416. //zip files
  3417. $zipper = new zip_packer();
  3418. if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
  3419. return $tempzip;
  3420. }
  3421. return false;
  3422. }
  3423. /**
  3424. * Lists all file areas current user may browse
  3425. *
  3426. * @package mod_assignment
  3427. * @category files
  3428. * @param stdClass $course course object
  3429. * @param stdClass $cm course module object
  3430. * @param stdClass $context context object
  3431. * @return array available file areas
  3432. */
  3433. function assignment_get_file_areas($course, $cm, $context) {
  3434. $areas = array();
  3435. if (has_capability('moodle/course:managefiles', $context)) {
  3436. $areas['submission'] = get_string('assignmentsubmission', 'assignment');
  3437. }
  3438. return $areas;
  3439. }
  3440. /**
  3441. * File browsing support for assignment module.
  3442. *
  3443. * @param file_browser $browser
  3444. * @param array $areas
  3445. * @param stdClass $course
  3446. * @param cm_info $cm
  3447. * @param context $context
  3448. * @param string $filearea
  3449. * @param int $itemid
  3450. * @param string $filepath
  3451. * @param string $filename
  3452. * @return file_info_stored file_info_stored instance or null if not found
  3453. */
  3454. function assignment_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
  3455. global $CFG, $DB, $USER;
  3456. if ($context->contextlevel != CONTEXT_MODULE || $filearea != 'submission') {
  3457. return null;
  3458. }
  3459. if (!$submission = $DB->get_record('assignment_submissions', array('id' => $itemid))) {
  3460. return null;
  3461. }
  3462. if (!(($submission->userid == $USER->id && has_capability('mod/assignment:view', $context))
  3463. || has_capability('mod/assignment:grade', $context))) {
  3464. // no permission to view this submission
  3465. return null;
  3466. }
  3467. $fs = get_file_storage();
  3468. $filepath = is_null($filepath) ? '/' : $filepath;
  3469. $filename = is_null($filename) ? '.' : $filename;
  3470. if (!($storedfile = $fs->get_file($context->id, 'mod_assignment', $filearea, $itemid, $filepath, $filename))) {
  3471. return null;
  3472. }
  3473. $urlbase = $CFG->wwwroot.'/pluginfile.php';
  3474. return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false);
  3475. }
  3476. /**
  3477. * Return a list of page types
  3478. * @param string $pagetype current page type
  3479. * @param stdClass $parentcontext Block's parent context
  3480. * @param stdClass $currentcontext Current context of block
  3481. */
  3482. function assignment_page_type_list($pagetype, $parentcontext, $currentcontext) {
  3483. $module_pagetype = array(
  3484. 'mod-assignment-*'=>get_string('page-mod-assignment-x', 'assignment'),
  3485. 'mod-assignment-view'=>get_string('page-mod-assignment-view', 'assignment'),
  3486. 'mod-assignment-submissions'=>get_string('page-mod-assignment-submissions', 'assignment')
  3487. );
  3488. return $module_pagetype;
  3489. }
  3490. /**
  3491. * Lists all gradable areas for the advanced grading methods gramework
  3492. *
  3493. * @return array
  3494. */
  3495. function assignment_grading_areas_list() {
  3496. return array('submission' => get_string('submissions', 'mod_assignment'));
  3497. }