PageRenderTime 230ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 1ms

/mod/assignment/lib.php

https://bitbucket.org/ngmares/moodle
PHP | 4044 lines | 3655 code | 136 blank | 253 comment | 113 complexity | b86c1dc381560e0982af409e73504077 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, BSD-3-Clause
  1. <?PHP
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * assignment_base is the base class for assignment types
  18. *
  19. * This class provides all the functionality for an assignment
  20. *
  21. * @package mod-assignment
  22. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24. */
  25. /** Include eventslib.php */
  26. require_once($CFG->libdir.'/eventslib.php');
  27. /** Include formslib.php */
  28. require_once($CFG->libdir.'/formslib.php');
  29. /** Include calendar/lib.php */
  30. require_once($CFG->dirroot.'/calendar/lib.php');
  31. /** ASSIGNMENT_COUNT_WORDS = 1 */
  32. define('ASSIGNMENT_COUNT_WORDS', 1);
  33. /** ASSIGNMENT_COUNT_LETTERS = 2 */
  34. define('ASSIGNMENT_COUNT_LETTERS', 2);
  35. /**
  36. * Standard base class for all assignment submodules (assignment types).
  37. *
  38. * @package mod-assignment
  39. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  40. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41. */
  42. class assignment_base {
  43. const FILTER_ALL = 0;
  44. const FILTER_SUBMITTED = 1;
  45. const FILTER_REQUIRE_GRADING = 2;
  46. /** @var object */
  47. var $cm;
  48. /** @var object */
  49. var $course;
  50. /** @var stdClass */
  51. var $coursecontext;
  52. /** @var object */
  53. var $assignment;
  54. /** @var string */
  55. var $strassignment;
  56. /** @var string */
  57. var $strassignments;
  58. /** @var string */
  59. var $strsubmissions;
  60. /** @var string */
  61. var $strlastmodified;
  62. /** @var string */
  63. var $pagetitle;
  64. /** @var bool */
  65. var $usehtmleditor;
  66. /**
  67. * @todo document this var
  68. */
  69. var $defaultformat;
  70. /**
  71. * @todo document this var
  72. */
  73. var $context;
  74. /** @var string */
  75. var $type;
  76. /**
  77. * Constructor for the base assignment class
  78. *
  79. * Constructor for the base assignment class.
  80. * If cmid is set create the cm, course, assignment objects.
  81. * If the assignment is hidden and the user is not a teacher then
  82. * this prints a page header and notice.
  83. *
  84. * @global object
  85. * @global object
  86. * @param int $cmid the current course module id - not set for new assignments
  87. * @param object $assignment usually null, but if we have it we pass it to save db access
  88. * @param object $cm usually null, but if we have it we pass it to save db access
  89. * @param object $course usually null, but if we have it we pass it to save db access
  90. */
  91. function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
  92. global $COURSE, $DB;
  93. if ($cmid == 'staticonly') {
  94. //use static functions only!
  95. return;
  96. }
  97. global $CFG;
  98. if ($cm) {
  99. $this->cm = $cm;
  100. } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
  101. print_error('invalidcoursemodule');
  102. }
  103. $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
  104. if ($course) {
  105. $this->course = $course;
  106. } else if ($this->cm->course == $COURSE->id) {
  107. $this->course = $COURSE;
  108. } else if (! $this->course = $DB->get_record('course', array('id'=>$this->cm->course))) {
  109. print_error('invalidid', 'assignment');
  110. }
  111. $this->coursecontext = get_context_instance(CONTEXT_COURSE, $this->course->id);
  112. $courseshortname = format_text($this->course->shortname, true, array('context' => $this->coursecontext));
  113. if ($assignment) {
  114. $this->assignment = $assignment;
  115. } else if (! $this->assignment = $DB->get_record('assignment', array('id'=>$this->cm->instance))) {
  116. print_error('invalidid', 'assignment');
  117. }
  118. $this->assignment->cmidnumber = $this->cm->idnumber; // compatibility with modedit assignment obj
  119. $this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj
  120. $this->strassignment = get_string('modulename', 'assignment');
  121. $this->strassignments = get_string('modulenameplural', 'assignment');
  122. $this->strsubmissions = get_string('submissions', 'assignment');
  123. $this->strlastmodified = get_string('lastmodified');
  124. $this->pagetitle = strip_tags($courseshortname.': '.$this->strassignment.': '.format_string($this->assignment->name, true, array('context' => $this->context)));
  125. // visibility handled by require_login() with $cm parameter
  126. // get current group only when really needed
  127. /// Set up things for a HTML editor if it's needed
  128. $this->defaultformat = editors_get_preferred_format();
  129. }
  130. /**
  131. * Display the assignment, used by view.php
  132. *
  133. * This in turn calls the methods producing individual parts of the page
  134. */
  135. function view() {
  136. $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
  137. require_capability('mod/assignment:view', $context);
  138. add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
  139. $this->assignment->id, $this->cm->id);
  140. $this->view_header();
  141. $this->view_intro();
  142. $this->view_dates();
  143. $this->view_feedback();
  144. $this->view_footer();
  145. }
  146. /**
  147. * Display the header and top of a page
  148. *
  149. * (this doesn't change much for assignment types)
  150. * This is used by the view() method to print the header of view.php but
  151. * it can be used on other pages in which case the string to denote the
  152. * page in the navigation trail should be passed as an argument
  153. *
  154. * @global object
  155. * @param string $subpage Description of subpage to be used in navigation trail
  156. */
  157. function view_header($subpage='') {
  158. global $CFG, $PAGE, $OUTPUT;
  159. if ($subpage) {
  160. $PAGE->navbar->add($subpage);
  161. }
  162. $PAGE->set_title($this->pagetitle);
  163. $PAGE->set_heading($this->course->fullname);
  164. echo $OUTPUT->header();
  165. groups_print_activity_menu($this->cm, $CFG->wwwroot . '/mod/assignment/view.php?id=' . $this->cm->id);
  166. echo '<div class="reportlink">'.$this->submittedlink().'</div>';
  167. echo '<div class="clearer"></div>';
  168. if (has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
  169. echo $OUTPUT->notification(get_string('upgradenotification', 'assignment'));
  170. $adminurl = new moodle_url('/admin/tool/assignmentupgrade/listnotupgraded.php');
  171. echo $OUTPUT->single_button($adminurl, get_string('viewassignmentupgradetool', 'assignment'));
  172. }
  173. }
  174. /**
  175. * Display the assignment intro
  176. *
  177. * This will most likely be extended by assignment type plug-ins
  178. * The default implementation prints the assignment description in a box
  179. */
  180. function view_intro() {
  181. global $OUTPUT;
  182. echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
  183. echo format_module_intro('assignment', $this->assignment, $this->cm->id);
  184. echo $OUTPUT->box_end();
  185. echo plagiarism_print_disclosure($this->cm->id);
  186. }
  187. /**
  188. * Display the assignment dates
  189. *
  190. * Prints the assignment start and end dates in a box.
  191. * This will be suitable for most assignment types
  192. */
  193. function view_dates() {
  194. global $OUTPUT;
  195. if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
  196. return;
  197. }
  198. echo $OUTPUT->box_start('generalbox boxaligncenter', 'dates');
  199. echo '<table>';
  200. if ($this->assignment->timeavailable) {
  201. echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
  202. echo ' <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
  203. }
  204. if ($this->assignment->timedue) {
  205. echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
  206. echo ' <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
  207. }
  208. echo '</table>';
  209. echo $OUTPUT->box_end();
  210. }
  211. /**
  212. * Display the bottom and footer of a page
  213. *
  214. * This default method just prints the footer.
  215. * This will be suitable for most assignment types
  216. */
  217. function view_footer() {
  218. global $OUTPUT;
  219. echo $OUTPUT->footer();
  220. }
  221. /**
  222. * Display the feedback to the student
  223. *
  224. * This default method prints the teacher picture and name, date when marked,
  225. * grade and teacher submissioncomment.
  226. * If advanced grading is used the method render_grade from the
  227. * advanced grading controller is called to display the grade.
  228. *
  229. * @global object
  230. * @global object
  231. * @global object
  232. * @param object $submission The submission object or NULL in which case it will be loaded
  233. */
  234. function view_feedback($submission=NULL) {
  235. global $USER, $CFG, $DB, $OUTPUT, $PAGE;
  236. require_once($CFG->libdir.'/gradelib.php');
  237. require_once("$CFG->dirroot/grade/grading/lib.php");
  238. if (!$submission) { /// Get submission for this assignment
  239. $userid = $USER->id;
  240. $submission = $this->get_submission($userid);
  241. } else {
  242. $userid = $submission->userid;
  243. }
  244. // Check the user can submit
  245. $canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
  246. // If not then check if the user still has the view cap and has a previous submission
  247. $canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
  248. // Or if user can grade (is a teacher or admin)
  249. $canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
  250. if (!$canviewfeedback) {
  251. // can not view or submit assignments -> no feedback
  252. return;
  253. }
  254. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
  255. $item = $grading_info->items[0];
  256. $grade = $item->grades[$userid];
  257. if ($grade->hidden or $grade->grade === false) { // hidden or error
  258. return;
  259. }
  260. if ($grade->grade === null and empty($grade->str_feedback)) { /// Nothing to show yet
  261. return;
  262. }
  263. $graded_date = $grade->dategraded;
  264. $graded_by = $grade->usermodified;
  265. /// We need the teacher info
  266. if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
  267. print_error('cannotfindteacher');
  268. }
  269. /// Print the feedback
  270. echo $OUTPUT->heading(get_string('feedbackfromteacher', 'assignment', fullname($teacher)));
  271. echo '<table cellspacing="0" class="feedback">';
  272. echo '<tr>';
  273. echo '<td class="left picture">';
  274. if ($teacher) {
  275. echo $OUTPUT->user_picture($teacher);
  276. }
  277. echo '</td>';
  278. echo '<td class="topic">';
  279. echo '<div class="from">';
  280. if ($teacher) {
  281. echo '<div class="fullname">'.fullname($teacher).'</div>';
  282. }
  283. echo '<div class="time">'.userdate($graded_date).'</div>';
  284. echo '</div>';
  285. echo '</td>';
  286. echo '</tr>';
  287. echo '<tr>';
  288. echo '<td class="left side">&nbsp;</td>';
  289. echo '<td class="content">';
  290. $gradestr = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
  291. if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
  292. $controller->set_grade_range(make_grades_menu($this->assignment->grade));
  293. echo $controller->render_grade($PAGE, $submission->id, $item, $gradestr, has_capability('mod/assignment:grade', $this->context));
  294. } else {
  295. echo $gradestr;
  296. }
  297. echo '<div class="clearer"></div>';
  298. echo '<div class="comment">';
  299. echo $grade->str_feedback;
  300. echo '</div>';
  301. echo '</tr>';
  302. if ($this->type == 'uploadsingle') { //@TODO: move to overload view_feedback method in the class or is uploadsingle merging into upload?
  303. $responsefiles = $this->print_responsefiles($submission->userid, true);
  304. if (!empty($responsefiles)) {
  305. echo '<tr>';
  306. echo '<td class="left side">&nbsp;</td>';
  307. echo '<td class="content">';
  308. echo $responsefiles;
  309. echo '</tr>';
  310. }
  311. }
  312. echo '</table>';
  313. }
  314. /**
  315. * Returns a link with info about the state of the assignment submissions
  316. *
  317. * This is used by view_header to put this link at the top right of the page.
  318. * For teachers it gives the number of submitted assignments with a link
  319. * For students it gives the time of their submission.
  320. * This will be suitable for most assignment types.
  321. *
  322. * @global object
  323. * @global object
  324. * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php
  325. * @return string
  326. */
  327. function submittedlink($allgroups=false) {
  328. global $USER;
  329. global $CFG;
  330. $submitted = '';
  331. $urlbase = "{$CFG->wwwroot}/mod/assignment/";
  332. $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
  333. if (has_capability('mod/assignment:grade', $context)) {
  334. if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) {
  335. $group = 0;
  336. } else {
  337. $group = groups_get_activity_group($this->cm);
  338. }
  339. if ($this->type == 'offline') {
  340. $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
  341. get_string('viewfeedback', 'assignment').'</a>';
  342. } else if ($count = $this->count_real_submissions($group)) {
  343. $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
  344. get_string('viewsubmissions', 'assignment', $count).'</a>';
  345. } else {
  346. $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
  347. get_string('noattempts', 'assignment').'</a>';
  348. }
  349. } else {
  350. if (isloggedin()) {
  351. if ($submission = $this->get_submission($USER->id)) {
  352. // If the submission has been completed
  353. if ($this->is_submitted_with_required_data($submission)) {
  354. if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
  355. $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
  356. } else {
  357. $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
  358. }
  359. }
  360. }
  361. }
  362. }
  363. return $submitted;
  364. }
  365. /**
  366. * Returns whether the assigment supports lateness information
  367. *
  368. * @return bool This assignment type supports lateness (true, default) or no (false)
  369. */
  370. function supports_lateness() {
  371. return true;
  372. }
  373. /**
  374. * @todo Document this function
  375. */
  376. function setup_elements(&$mform) {
  377. }
  378. /**
  379. * Any preprocessing needed for the settings form for
  380. * this assignment type
  381. *
  382. * @param array $default_values - array to fill in with the default values
  383. * in the form 'formelement' => 'value'
  384. * @param object $form - the form that is to be displayed
  385. * @return none
  386. */
  387. function form_data_preprocessing(&$default_values, $form) {
  388. }
  389. /**
  390. * Any extra validation checks needed for the settings
  391. * form for this assignment type
  392. *
  393. * See lib/formslib.php, 'validation' function for details
  394. */
  395. function form_validation($data, $files) {
  396. return array();
  397. }
  398. /**
  399. * Create a new assignment activity
  400. *
  401. * Given an object containing all the necessary data,
  402. * (defined by the form in mod_form.php) this function
  403. * will create a new instance and return the id number
  404. * of the new instance.
  405. * The due data is added to the calendar
  406. * This is common to all assignment types.
  407. *
  408. * @global object
  409. * @global object
  410. * @param object $assignment The data from the form on mod_form.php
  411. * @return int The id of the assignment
  412. */
  413. function add_instance($assignment) {
  414. global $COURSE, $DB;
  415. $assignment->timemodified = time();
  416. $assignment->courseid = $assignment->course;
  417. $returnid = $DB->insert_record("assignment", $assignment);
  418. $assignment->id = $returnid;
  419. if ($assignment->timedue) {
  420. $event = new stdClass();
  421. $event->name = $assignment->name;
  422. $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
  423. $event->courseid = $assignment->course;
  424. $event->groupid = 0;
  425. $event->userid = 0;
  426. $event->modulename = 'assignment';
  427. $event->instance = $returnid;
  428. $event->eventtype = 'due';
  429. $event->timestart = $assignment->timedue;
  430. $event->timeduration = 0;
  431. calendar_event::create($event);
  432. }
  433. assignment_grade_item_update($assignment);
  434. return $returnid;
  435. }
  436. /**
  437. * Deletes an assignment activity
  438. *
  439. * Deletes all database records, files and calendar events for this assignment.
  440. *
  441. * @global object
  442. * @global object
  443. * @param object $assignment The assignment to be deleted
  444. * @return boolean False indicates error
  445. */
  446. function delete_instance($assignment) {
  447. global $CFG, $DB;
  448. $assignment->courseid = $assignment->course;
  449. $result = true;
  450. // now get rid of all files
  451. $fs = get_file_storage();
  452. if ($cm = get_coursemodule_from_instance('assignment', $assignment->id)) {
  453. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  454. $fs->delete_area_files($context->id);
  455. }
  456. if (! $DB->delete_records('assignment_submissions', array('assignment'=>$assignment->id))) {
  457. $result = false;
  458. }
  459. if (! $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
  460. $result = false;
  461. }
  462. if (! $DB->delete_records('assignment', array('id'=>$assignment->id))) {
  463. $result = false;
  464. }
  465. $mod = $DB->get_field('modules','id',array('name'=>'assignment'));
  466. assignment_grade_item_delete($assignment);
  467. return $result;
  468. }
  469. /**
  470. * Updates a new assignment activity
  471. *
  472. * Given an object containing all the necessary data,
  473. * (defined by the form in mod_form.php) this function
  474. * will update the assignment instance and return the id number
  475. * The due date is updated in the calendar
  476. * This is common to all assignment types.
  477. *
  478. * @global object
  479. * @global object
  480. * @param object $assignment The data from the form on mod_form.php
  481. * @return bool success
  482. */
  483. function update_instance($assignment) {
  484. global $COURSE, $DB;
  485. $assignment->timemodified = time();
  486. $assignment->id = $assignment->instance;
  487. $assignment->courseid = $assignment->course;
  488. $DB->update_record('assignment', $assignment);
  489. if ($assignment->timedue) {
  490. $event = new stdClass();
  491. if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
  492. $event->name = $assignment->name;
  493. $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
  494. $event->timestart = $assignment->timedue;
  495. $calendarevent = calendar_event::load($event->id);
  496. $calendarevent->update($event);
  497. } else {
  498. $event = new stdClass();
  499. $event->name = $assignment->name;
  500. $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
  501. $event->courseid = $assignment->course;
  502. $event->groupid = 0;
  503. $event->userid = 0;
  504. $event->modulename = 'assignment';
  505. $event->instance = $assignment->id;
  506. $event->eventtype = 'due';
  507. $event->timestart = $assignment->timedue;
  508. $event->timeduration = 0;
  509. calendar_event::create($event);
  510. }
  511. } else {
  512. $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id));
  513. }
  514. // get existing grade item
  515. assignment_grade_item_update($assignment);
  516. return true;
  517. }
  518. /**
  519. * Update grade item for this submission.
  520. *
  521. * @param stdClass $submission The submission instance
  522. */
  523. function update_grade($submission) {
  524. assignment_update_grades($this->assignment, $submission->userid);
  525. }
  526. /**
  527. * Top-level function for handling of submissions called by submissions.php
  528. *
  529. * This is for handling the teacher interaction with the grading interface
  530. * This should be suitable for most assignment types.
  531. *
  532. * @global object
  533. * @param string $mode Specifies the kind of teacher interaction taking place
  534. */
  535. function submissions($mode) {
  536. ///The main switch is changed to facilitate
  537. ///1) Batch fast grading
  538. ///2) Skip to the next one on the popup
  539. ///3) Save and Skip to the next one on the popup
  540. //make user global so we can use the id
  541. global $USER, $OUTPUT, $DB, $PAGE;
  542. $mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
  543. if (optional_param('next', null, PARAM_BOOL)) {
  544. $mode='next';
  545. }
  546. if (optional_param('saveandnext', null, PARAM_BOOL)) {
  547. $mode='saveandnext';
  548. }
  549. if (is_null($mailinfo)) {
  550. if (optional_param('sesskey', null, PARAM_BOOL)) {
  551. set_user_preference('assignment_mailinfo', 0);
  552. } else {
  553. $mailinfo = get_user_preferences('assignment_mailinfo', 0);
  554. }
  555. } else {
  556. set_user_preference('assignment_mailinfo', $mailinfo);
  557. }
  558. if (!($this->validate_and_preprocess_feedback())) {
  559. // form was submitted ('Save' or 'Save and next' was pressed, but validation failed)
  560. $this->display_submission();
  561. return;
  562. }
  563. switch ($mode) {
  564. case 'grade': // We are in a main window grading
  565. if ($submission = $this->process_feedback()) {
  566. $this->display_submissions(get_string('changessaved'));
  567. } else {
  568. $this->display_submissions();
  569. }
  570. break;
  571. case 'single': // We are in a main window displaying one submission
  572. if ($submission = $this->process_feedback()) {
  573. $this->display_submissions(get_string('changessaved'));
  574. } else {
  575. $this->display_submission();
  576. }
  577. break;
  578. case 'all': // Main window, display everything
  579. $this->display_submissions();
  580. break;
  581. case 'fastgrade':
  582. ///do the fast grading stuff - this process should work for all 3 subclasses
  583. $grading = false;
  584. $commenting = false;
  585. $col = false;
  586. if (isset($_POST['submissioncomment'])) {
  587. $col = 'submissioncomment';
  588. $commenting = true;
  589. }
  590. if (isset($_POST['menu'])) {
  591. $col = 'menu';
  592. $grading = true;
  593. }
  594. if (!$col) {
  595. //both submissioncomment and grade columns collapsed..
  596. $this->display_submissions();
  597. break;
  598. }
  599. foreach ($_POST[$col] as $id => $unusedvalue){
  600. $id = (int)$id; //clean parameter name
  601. $this->process_outcomes($id);
  602. if (!$submission = $this->get_submission($id)) {
  603. $submission = $this->prepare_new_submission($id);
  604. $newsubmission = true;
  605. } else {
  606. $newsubmission = false;
  607. }
  608. unset($submission->data1); // Don't need to update this.
  609. unset($submission->data2); // Don't need to update this.
  610. //for fast grade, we need to check if any changes take place
  611. $updatedb = false;
  612. if ($grading) {
  613. $grade = $_POST['menu'][$id];
  614. $updatedb = $updatedb || ($submission->grade != $grade);
  615. $submission->grade = $grade;
  616. } else {
  617. if (!$newsubmission) {
  618. unset($submission->grade); // Don't need to update this.
  619. }
  620. }
  621. if ($commenting) {
  622. $commentvalue = trim($_POST['submissioncomment'][$id]);
  623. $updatedb = $updatedb || ($submission->submissioncomment != $commentvalue);
  624. $submission->submissioncomment = $commentvalue;
  625. } else {
  626. unset($submission->submissioncomment); // Don't need to update this.
  627. }
  628. $submission->teacher = $USER->id;
  629. if ($updatedb) {
  630. $submission->mailed = (int)(!$mailinfo);
  631. }
  632. $submission->timemarked = time();
  633. //if it is not an update, we don't change the last modified time etc.
  634. //this will also not write into database if no submissioncomment and grade is entered.
  635. if ($updatedb){
  636. if ($newsubmission) {
  637. if (!isset($submission->submissioncomment)) {
  638. $submission->submissioncomment = '';
  639. }
  640. $sid = $DB->insert_record('assignment_submissions', $submission);
  641. $submission->id = $sid;
  642. } else {
  643. $DB->update_record('assignment_submissions', $submission);
  644. }
  645. // trigger grade event
  646. $this->update_grade($submission);
  647. //add to log only if updating
  648. add_to_log($this->course->id, 'assignment', 'update grades',
  649. 'submissions.php?id='.$this->cm->id.'&user='.$submission->userid,
  650. $submission->userid, $this->cm->id);
  651. }
  652. }
  653. $message = $OUTPUT->notification(get_string('changessaved'), 'notifysuccess');
  654. $this->display_submissions($message);
  655. break;
  656. case 'saveandnext':
  657. ///We are in pop up. save the current one and go to the next one.
  658. //first we save the current changes
  659. if ($submission = $this->process_feedback()) {
  660. //print_heading(get_string('changessaved'));
  661. //$extra_javascript = $this->update_main_listing($submission);
  662. }
  663. case 'next':
  664. /// We are currently in pop up, but we want to skip to next one without saving.
  665. /// This turns out to be similar to a single case
  666. /// The URL used is for the next submission.
  667. $offset = required_param('offset', PARAM_INT);
  668. $nextid = required_param('nextid', PARAM_INT);
  669. $id = required_param('id', PARAM_INT);
  670. $filter = optional_param('filter', self::FILTER_ALL, PARAM_INT);
  671. if ($mode == 'next' || $filter !== self::FILTER_REQUIRE_GRADING) {
  672. $offset = (int)$offset+1;
  673. }
  674. $redirect = new moodle_url('submissions.php',
  675. array('id' => $id, 'offset' => $offset, 'userid' => $nextid,
  676. 'mode' => 'single', 'filter' => $filter));
  677. redirect($redirect);
  678. break;
  679. case 'singlenosave':
  680. $this->display_submission();
  681. break;
  682. default:
  683. echo "something seriously is wrong!!";
  684. break;
  685. }
  686. }
  687. /**
  688. * Checks if grading method allows quickgrade mode. At the moment it is hardcoded
  689. * that advanced grading methods do not allow quickgrade.
  690. *
  691. * Assignment type plugins are not allowed to override this method
  692. *
  693. * @return boolean
  694. */
  695. public final function quickgrade_mode_allowed() {
  696. global $CFG;
  697. require_once("$CFG->dirroot/grade/grading/lib.php");
  698. if ($controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
  699. return false;
  700. }
  701. return true;
  702. }
  703. /**
  704. * Helper method updating the listing on the main script from popup using javascript
  705. *
  706. * @global object
  707. * @global object
  708. * @param $submission object The submission whose data is to be updated on the main page
  709. */
  710. function update_main_listing($submission) {
  711. global $SESSION, $CFG, $OUTPUT;
  712. $output = '';
  713. $perpage = get_user_preferences('assignment_perpage', 10);
  714. $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
  715. /// Run some Javascript to try and update the parent page
  716. $output .= '<script type="text/javascript">'."\n<!--\n";
  717. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
  718. if ($quickgrade){
  719. $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
  720. .trim($submission->submissioncomment).'";'."\n";
  721. } else {
  722. $output.= 'opener.document.getElementById("com'.$submission->userid.
  723. '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
  724. }
  725. }
  726. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
  727. //echo optional_param('menuindex');
  728. if ($quickgrade){
  729. $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
  730. '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
  731. } else {
  732. $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
  733. $this->display_grade($submission->grade)."\";\n";
  734. }
  735. }
  736. //need to add student's assignments in there too.
  737. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
  738. $submission->timemodified) {
  739. $output.= 'opener.document.getElementById("ts'.$submission->userid.
  740. '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
  741. }
  742. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
  743. $submission->timemarked) {
  744. $output.= 'opener.document.getElementById("tt'.$submission->userid.
  745. '").innerHTML="'.userdate($submission->timemarked)."\";\n";
  746. }
  747. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
  748. $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
  749. $buttontext = get_string('update');
  750. $url = new moodle_url('/mod/assignment/submissions.php', array(
  751. 'id' => $this->cm->id,
  752. 'userid' => $submission->userid,
  753. 'mode' => 'single',
  754. 'offset' => (optional_param('offset', '', PARAM_INT)-1)));
  755. $button = $OUTPUT->action_link($url, $buttontext, new popup_action('click', $url, 'grade'.$submission->userid, array('height' => 450, 'width' => 700)), array('ttile'=>$buttontext));
  756. $output .= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
  757. }
  758. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
  759. if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
  760. $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
  761. '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
  762. }
  763. if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
  764. if (!empty($grading_info->outcomes)) {
  765. foreach($grading_info->outcomes as $n=>$outcome) {
  766. if ($outcome->grades[$submission->userid]->locked) {
  767. continue;
  768. }
  769. if ($quickgrade){
  770. $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
  771. '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
  772. } else {
  773. $options = make_grades_menu(-$outcome->scaleid);
  774. $options[0] = get_string('nooutcome', 'grades');
  775. $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
  776. }
  777. }
  778. }
  779. }
  780. $output .= "\n-->\n</script>";
  781. return $output;
  782. }
  783. /**
  784. * Return a grade in user-friendly form, whether it's a scale or not
  785. *
  786. * @global object
  787. * @param mixed $grade
  788. * @return string User-friendly representation of grade
  789. */
  790. function display_grade($grade) {
  791. global $DB;
  792. static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
  793. if ($this->assignment->grade >= 0) { // Normal number
  794. if ($grade == -1) {
  795. return '-';
  796. } else {
  797. return $grade.' / '.$this->assignment->grade;
  798. }
  799. } else { // Scale
  800. if (empty($scalegrades[$this->assignment->id])) {
  801. if ($scale = $DB->get_record('scale', array('id'=>-($this->assignment->grade)))) {
  802. $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
  803. } else {
  804. return '-';
  805. }
  806. }
  807. if (isset($scalegrades[$this->assignment->id][$grade])) {
  808. return $scalegrades[$this->assignment->id][$grade];
  809. }
  810. return '-';
  811. }
  812. }
  813. /**
  814. * Display a single submission, ready for grading on a popup window
  815. *
  816. * This default method prints the teacher info and submissioncomment box at the top and
  817. * the student info and submission at the bottom.
  818. * This method also fetches the necessary data in order to be able to
  819. * provide a "Next submission" button.
  820. * Calls preprocess_submission() to give assignment type plug-ins a chance
  821. * to process submissions before they are graded
  822. * This method gets its arguments from the page parameters userid and offset
  823. *
  824. * @global object
  825. * @global object
  826. * @param string $extra_javascript
  827. */
  828. function display_submission($offset=-1,$userid =-1, $display=true) {
  829. global $CFG, $DB, $PAGE, $OUTPUT, $USER;
  830. require_once($CFG->libdir.'/gradelib.php');
  831. require_once($CFG->libdir.'/tablelib.php');
  832. require_once("$CFG->dirroot/repository/lib.php");
  833. require_once("$CFG->dirroot/grade/grading/lib.php");
  834. if ($userid==-1) {
  835. $userid = required_param('userid', PARAM_INT);
  836. }
  837. if ($offset==-1) {
  838. $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
  839. }
  840. $filter = optional_param('filter', 0, PARAM_INT);
  841. if (!$user = $DB->get_record('user', array('id'=>$userid))) {
  842. print_error('nousers');
  843. }
  844. if (!$submission = $this->get_submission($user->id)) {
  845. $submission = $this->prepare_new_submission($userid);
  846. }
  847. if ($submission->timemodified > $submission->timemarked) {
  848. $subtype = 'assignmentnew';
  849. } else {
  850. $subtype = 'assignmentold';
  851. }
  852. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
  853. $gradingdisabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
  854. /// construct SQL, using current offset to find the data of the next student
  855. $course = $this->course;
  856. $assignment = $this->assignment;
  857. $cm = $this->cm;
  858. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  859. //reset filter to all for offline assignment
  860. if ($assignment->assignmenttype == 'offline' && $filter == self::FILTER_SUBMITTED) {
  861. $filter = self::FILTER_ALL;
  862. }
  863. /// Get all ppl that can submit assignments
  864. $currentgroup = groups_get_activity_group($cm);
  865. $users = get_enrolled_users($context, 'mod/assignment:submit', $currentgroup, 'u.id');
  866. if ($users) {
  867. $users = array_keys($users);
  868. // if groupmembersonly used, remove users who are not in any group
  869. if (!empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
  870. if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
  871. $users = array_intersect($users, array_keys($groupingusers));
  872. }
  873. }
  874. }
  875. $nextid = 0;
  876. $where = '';
  877. if($filter == self::FILTER_SUBMITTED) {
  878. $where .= 's.timemodified > 0 AND ';
  879. } else if($filter == self::FILTER_REQUIRE_GRADING) {
  880. $where .= 's.timemarked < s.timemodified AND ';
  881. }
  882. if ($users) {
  883. $userfields = user_picture::fields('u', array('lastaccess'));
  884. $select = "SELECT $userfields,
  885. s.id AS submissionid, s.grade, s.submissioncomment,
  886. s.timemodified, s.timemarked,
  887. CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
  888. ELSE 0 END AS status ";
  889. $sql = 'FROM {user} u '.
  890. 'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
  891. AND s.assignment = '.$this->assignment->id.' '.
  892. 'WHERE '.$where.'u.id IN ('.implode(',', $users).') ';
  893. if ($sort = flexible_table::get_sort_for_table('mod-assignment-submissions')) {
  894. $sort = 'ORDER BY '.$sort.' ';
  895. }
  896. $auser = $DB->get_records_sql($select.$sql.$sort, null, $offset, 2);
  897. if (is_array($auser) && count($auser)>1) {
  898. $nextuser = next($auser);
  899. $nextid = $nextuser->id;
  900. }
  901. }
  902. if ($submission->teacher) {
  903. $teacher = $DB->get_record('user', array('id'=>$submission->teacher));
  904. } else {
  905. global $USER;
  906. $teacher = $USER;
  907. }
  908. $this->preprocess_submission($submission);
  909. $mformdata = new stdClass();
  910. $mformdata->context = $this->context;
  911. $mformdata->maxbytes = $this->course->maxbytes;
  912. $mformdata->courseid = $this->course->id;
  913. $mformdata->teacher = $teacher;
  914. $mformdata->assignment = $assignment;
  915. $mformdata->submission = $submission;
  916. $mformdata->lateness = $this->display_lateness($submission->timemodified);
  917. $mformdata->auser = $auser;
  918. $mformdata->user = $user;
  919. $mformdata->offset = $offset;
  920. $mformdata->userid = $userid;
  921. $mformdata->cm = $this->cm;
  922. $mformdata->grading_info = $grading_info;
  923. $mformdata->enableoutcomes = $CFG->enableoutcomes;
  924. $mformdata->grade = $this->assignment->grade;
  925. $mformdata->gradingdisabled = $gradingdisabled;
  926. $mformdata->nextid = $nextid;
  927. $mformdata->submissioncomment= $submission->submissioncomment;
  928. $mformdata->submissioncommentformat= FORMAT_HTML;
  929. $mformdata->submission_content= $this->print_user_files($user->id,true);
  930. $mformdata->filter = $filter;
  931. $mformdata->mailinfo = get_user_preferences('assignment_mailinfo', 0);
  932. if ($assignment->assignmenttype == 'upload') {
  933. $mformdata->fileui_options = array('subdirs'=>1, 'maxbytes'=>$assignment->maxbytes, 'maxfiles'=>$assignment->var1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
  934. } elseif ($assignment->assignmenttype == 'uploadsingle') {
  935. $mformdata->fileui_options = array('subdirs'=>0, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
  936. }
  937. $advancedgradingwarning = false;
  938. $gradingmanager = get_grading_manager($this->context, 'mod_assignment', 'submission');
  939. if ($gradingmethod = $gradingmanager->get_active_method()) {
  940. $controller = $gradingmanager->get_controller($gradingmethod);
  941. if ($controller->is_form_available()) {
  942. $itemid = null;
  943. if (!empty($submission->id)) {
  944. $itemid = $submission->id;
  945. }
  946. if ($gradingdisabled && $itemid) {
  947. $mformdata->advancedgradinginstance = $controller->get_current_instance($USER->id, $itemid);
  948. } else if (!$gradingdisabled) {
  949. $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
  950. $mformdata->advancedgradinginstance = $controller->get_or_create_instance($instanceid, $USER->id, $itemid);
  951. }
  952. } else {
  953. $advancedgradingwarning = $controller->form_unavailable_notification();
  954. }
  955. }
  956. $submitform = new assignment_grading_form( null, $mformdata );
  957. if (!$display) {
  958. $ret_data = new stdClass();
  959. $ret_data->mform = $submitform;
  960. if (isset($mformdata->fileui_options)) {
  961. $ret_data->fileui_options = $mformdata->fileui_options;
  962. }
  963. return $ret_data;
  964. }
  965. if ($submitform->is_cancelled()) {
  966. redirect('submissions.php?id='.$this->cm->id);
  967. }
  968. $submitform->set_data($mformdata);
  969. $PAGE->set_title($this->course->fullname . ': ' .get_string('feedback', 'assignment').' - '.fullname($user, true));
  970. $PAGE->set_heading($this->course->fullname);
  971. $PAGE->navbar->add(get_string('submissions', 'assignment'), new moodle_url('/mod/assignment/submissions.php', array('id'=>$cm->id)));
  972. $PAGE->navbar->add(fullname($user, true));
  973. echo $OUTPUT->header();
  974. echo $OUTPUT->heading(get_string('feedback', 'assignment').': '.fullname($user, true));
  975. // display mform here...
  976. if ($advancedgradingwarning) {
  977. echo $OUTPUT->notification($advancedgradingwarning, 'error');
  978. }
  979. $submitform->display();
  980. $customfeedback = $this->custom_feedbackform($submission, true);
  981. if (!empty($customfeedback)) {
  982. echo $customfeedback;
  983. }
  984. echo $OUTPUT->footer();
  985. }
  986. /**
  987. * Preprocess submission before grading
  988. *
  989. * Called by display_submission()
  990. * The default type does nothing here.
  991. *
  992. * @param object $submission The submission object
  993. */
  994. function preprocess_submission(&$submission) {
  995. }
  996. /**
  997. * Display all the submissions ready for grading
  998. *
  999. * @global object
  1000. * @global object
  1001. * @global object
  1002. * @global object
  1003. * @param string $message
  1004. * @return bool|void
  1005. */
  1006. function display_submissions($message='') {
  1007. global $CFG, $DB, $USER, $DB, $OUTPUT, $PAGE;
  1008. require_once($CFG->libdir.'/gradelib.php');
  1009. /* first we check to see if the form has just been submitted
  1010. * to request user_preference updates
  1011. */
  1012. $filters = array(self::FILTER_ALL => get_string('all'),
  1013. self::FILTER_REQUIRE_GRADING => get_string('requiregrading', 'assignment'));
  1014. $updatepref = optional_param('updatepref', 0, PARAM_BOOL);
  1015. if ($updatepref) {
  1016. $perpage = optional_param('perpage', 10, PARAM_INT);
  1017. $perpage = ($perpage <= 0) ? 10 : $perpage ;
  1018. $filter = optional_param('filter', 0, PARAM_INT);
  1019. set_user_preference('assignment_perpage', $perpage);
  1020. set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL));
  1021. set_user_preference('assignment_filter', $filter);
  1022. }
  1023. /* next we get perpage and quickgrade (allow quick grade) params
  1024. * from database
  1025. */
  1026. $perpage = get_user_preferences('assignment_perpage', 10);
  1027. $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
  1028. $filter = get_user_preferences('assignment_filter', 0);
  1029. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
  1030. if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
  1031. $uses_outcomes = true;
  1032. } else {
  1033. $uses_outcomes = false;
  1034. }
  1035. $page = optional_param('page', 0, PARAM_INT);
  1036. $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
  1037. /// Some shortcuts to make the code read better
  1038. $course = $this->course;
  1039. $assignment = $this->assignment;
  1040. $cm = $this->cm;
  1041. $hassubmission = false;
  1042. // reset filter to all for offline assignment only.
  1043. if ($assignment->assignmenttype == 'offline') {
  1044. if ($filter == self::FILTER_SUBMITTED) {
  1045. $filter = self::FILTER_ALL;
  1046. }
  1047. } else {
  1048. $filters[self::FILTER_SUBMITTED] = get_string('submitted', 'assignment');
  1049. }
  1050. $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
  1051. add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id, $this->assignment->id, $this->cm->id);
  1052. $PAGE->set_title(format_string($this->assignment->name,true));
  1053. $PAGE->set_heading($this->course->fullname);
  1054. echo $OUTPUT->header();
  1055. echo '<div class="usersubmissions">';
  1056. //hook to allow plagiarism plugins to update status/print links.
  1057. echo plagiarism_update_status($this->course, $this->cm);
  1058. $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
  1059. if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
  1060. echo '<div class="allcoursegrades"><a href="' . $CFG->wwwroot . '/grade/report/grader/index.php?id=' . $course->id . '">'
  1061. . get_string('seeallcoursegrades', 'grades') . '</a></div>';
  1062. }
  1063. if (!empty($message)) {
  1064. echo $message; // display messages here if any
  1065. }
  1066. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  1067. /// Check to see if groups are being used in this assignment
  1068. /// find out current groups mode
  1069. $groupmode = groups_get_activity_groupmode($cm);
  1070. $currentgroup = groups_get_activity_group($cm, true);
  1071. groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/assignment/submissions.php?id=' . $this->cm->id);
  1072. /// Print quickgrade form around the table
  1073. if ($quickgrade) {
  1074. $formattrs = array();
  1075. $formattrs['action'] = new moodle_url('/mod/assignment/submissions.php');
  1076. $formattrs['id'] = 'fastg';
  1077. $formattrs['method'] = 'post';
  1078. echo html_writer::start_tag('form', $formattrs);
  1079. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'id', 'value'=> $this->cm->id));
  1080. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'mode', 'value'=> 'fastgrade'));
  1081. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'page', 'value'=> $page));
  1082. echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=> sesskey()));
  1083. }
  1084. /// Get all ppl that are allowed to submit assignments
  1085. list($esql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $currentgroup);
  1086. if ($filter == self::FILTER_ALL) {
  1087. $sql = "SELECT u.id FROM {user} u ".
  1088. "LEFT JOIN ($esql) eu ON eu.id=u.id ".
  1089. "WHERE u.deleted = 0 AND eu.id=u.id ";
  1090. } else {
  1091. $wherefilter = ' AND s.assignment = '. $this->assignment->id;
  1092. $assignmentsubmission = "LEFT JOIN {assignment_submissions} s ON (u.id = s.userid) ";
  1093. if($filter == self::FILTER_SUBMITTED) {
  1094. $wherefilter .= ' AND s.timemodified > 0 ';
  1095. } else if($filter == self::FILTER_REQUIRE_GRADING && $assignment->assignmenttype != 'offline') {
  1096. $wherefilter .= ' AND s.timemarked < s.timemodified ';
  1097. } else { // require grading for offline assignment
  1098. $assignmentsubmission = "";
  1099. $wherefilter = "";
  1100. }
  1101. $sql = "SELECT u.id FROM {user} u ".
  1102. "LEFT JOIN ($esql) eu ON eu.id=u.id ".
  1103. $assignmentsubmission.
  1104. "WHERE u.deleted = 0 AND eu.id=u.id ".
  1105. $wherefilter;
  1106. }
  1107. $users = $DB->get_records_sql($sql, $params);
  1108. if (!empty($users)) {
  1109. if($assignment->assignmenttype == 'offline' && $filter == self::FILTER_REQUIRE_GRADING) {
  1110. //remove users who has submitted their assignment
  1111. foreach ($this->get_submissions() as $submission) {
  1112. if (array_key_exists($submission->userid, $users)) {
  1113. unset($users[$submission->userid]);
  1114. }
  1115. }
  1116. }
  1117. $users = array_keys($users);
  1118. }
  1119. // if groupmembersonly used, remove users who are not in any group
  1120. if ($users and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
  1121. if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
  1122. $users = array_intersect($users, array_keys($groupingusers));
  1123. }
  1124. }
  1125. $extrafields = get_extra_user_fields($context);
  1126. $tablecolumns = array_merge(array('picture', 'fullname'), $extrafields,
  1127. array('grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade'));
  1128. if ($uses_outcomes) {
  1129. $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
  1130. }
  1131. $extrafieldnames = array();
  1132. foreach ($extrafields as $field) {
  1133. $extrafieldnames[] = get_user_field_name($field);
  1134. }
  1135. $tableheaders = array_merge(
  1136. array('', get_string('fullnameuser')),
  1137. $extrafieldnames,
  1138. array(
  1139. get_string('grade'),
  1140. get_string('comment', 'assignment'),
  1141. get_string('lastmodified').' ('.get_string('submission', 'assignment').')',
  1142. get_string('lastmodified').' ('.get_string('grade').')',
  1143. get_string('status'),
  1144. get_string('finalgrade', 'grades'),
  1145. ));
  1146. if ($uses_outcomes) {
  1147. $tableheaders[] = get_string('outcome', 'grades');
  1148. }
  1149. require_once($CFG->libdir.'/tablelib.php');
  1150. $table = new flexible_table('mod-assignment-submissions');
  1151. $table->define_columns($tablecolumns);
  1152. $table->define_headers($tableheaders);
  1153. $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
  1154. $table->sortable(true, 'lastname');//sorted by lastname by default
  1155. $table->collapsible(true);
  1156. $table->initialbars(true);
  1157. $table->column_suppress('picture');
  1158. $table->column_suppress('fullname');
  1159. $table->column_class('picture', 'picture');
  1160. $table->column_class('fullname', 'fullname');
  1161. foreach ($extrafields as $field) {
  1162. $table->column_class($field, $field);
  1163. }
  1164. $table->column_class('grade', 'grade');
  1165. $table->column_class('submissioncomment', 'comment');
  1166. $table->column_class('timemodified', 'timemodified');
  1167. $table->column_class('timemarked', 'timemarked');
  1168. $table->column_class('status', 'status');
  1169. $table->column_class('finalgrade', 'finalgrade');
  1170. if ($uses_outcomes) {
  1171. $table->column_class('outcome', 'outcome');
  1172. }
  1173. $table->set_attribute('cellspacing', '0');
  1174. $table->set_attribute('id', 'attempts');
  1175. $table->set_attribute('class', 'submissions');
  1176. $table->set_attribute('width', '100%');
  1177. $table->no_sorting('finalgrade');
  1178. $table->no_sorting('outcome');
  1179. // Start working -- this is necessary as soon as the niceties are over
  1180. $table->setup();
  1181. /// Construct the SQL
  1182. list($where, $params) = $table->get_sql_where();
  1183. if ($where) {
  1184. $where .= ' AND ';
  1185. }
  1186. if ($filter == self::FILTER_SUBMITTED) {
  1187. $where .= 's.timemodified > 0 AND ';
  1188. } else if($filter == self::FILTER_REQUIRE_GRADING) {
  1189. $where = '';
  1190. if ($assignment->assignmenttype != 'offline') {
  1191. $where .= 's.timemarked < s.timemodified AND ';
  1192. }
  1193. }
  1194. if ($sort = $table->get_sql_sort()) {
  1195. $sort = ' ORDER BY '.$sort;
  1196. }
  1197. $ufields = user_picture::fields('u', $extrafields);
  1198. if (!empty($users)) {
  1199. $select = "SELECT $ufields,
  1200. s.id AS submissionid, s.grade, s.submissioncomment,
  1201. s.timemodified, s.timemarked,
  1202. CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
  1203. ELSE 0 END AS status ";
  1204. $sql = 'FROM {user} u '.
  1205. 'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
  1206. AND s.assignment = '.$this->assignment->id.' '.
  1207. 'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
  1208. $ausers = $DB->get_records_sql($select.$sql.$sort, $params, $table->get_page_start(), $table->get_page_size());
  1209. $table->pagesize($perpage, count($users));
  1210. ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
  1211. $offset = $page * $perpage;
  1212. $strupdate = get_string('update');
  1213. $strgrade = get_string('grade');
  1214. $strview = get_string('view');
  1215. $grademenu = make_grades_menu($this->assignment->grade);
  1216. if ($ausers !== false) {
  1217. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
  1218. $endposition = $offset + $perpage;
  1219. $currentposition = 0;
  1220. foreach ($ausers as $auser) {
  1221. if ($currentposition == $offset && $offset < $endposition) {
  1222. $rowclass = null;
  1223. $final_grade = $grading_info->items[0]->grades[$auser->id];
  1224. $grademax = $grading_info->items[0]->grademax;
  1225. $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
  1226. $locked_overridden = 'locked';
  1227. if ($final_grade->overridden) {
  1228. $locked_overridden = 'overridden';
  1229. }
  1230. // TODO add here code if advanced grading grade must be reviewed => $auser->status=0
  1231. $picture = $OUTPUT->user_picture($auser);
  1232. if (empty($auser->submissionid)) {
  1233. $auser->grade = -1; //no submission yet
  1234. }
  1235. if (!empty($auser->submissionid)) {
  1236. $hassubmission = true;
  1237. ///Prints student answer and student modified date
  1238. ///attach file or print link to student answer, depending on the type of the assignment.
  1239. ///Refer to print_student_answer in inherited classes.
  1240. if ($auser->timemodified > 0) {
  1241. $studentmodifiedcontent = $this->print_student_answer($auser->id)
  1242. . userdate($auser->timemodified);
  1243. if ($assignment->timedue && $auser->timemodified > $assignment->timedue && $this->supports_lateness()) {
  1244. $studentmodifiedcontent .= $this->display_lateness($auser->timemodified);
  1245. $rowclass = 'late';
  1246. }
  1247. } else {
  1248. $studentmodifiedcontent = '&nbsp;';
  1249. }
  1250. $studentmodified = html_writer::tag('div', $studentmodifiedcontent, array('id' => 'ts' . $auser->id));
  1251. ///Print grade, dropdown or text
  1252. if ($auser->timemarked > 0) {
  1253. $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
  1254. if ($final_grade->locked or $final_grade->overridden) {
  1255. $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
  1256. } else if ($quickgrade) {
  1257. $attributes = array();
  1258. $attributes['tabindex'] = $tabindex++;
  1259. $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
  1260. $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
  1261. } else {
  1262. $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
  1263. }
  1264. } else {
  1265. $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
  1266. if ($final_grade->locked or $final_grade->overridden) {
  1267. $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
  1268. } else if ($quickgrade) {
  1269. $attributes = array();
  1270. $attributes['tabindex'] = $tabindex++;
  1271. $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
  1272. $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
  1273. } else {
  1274. $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
  1275. }
  1276. }
  1277. ///Print Comment
  1278. if ($final_grade->locked or $final_grade->overridden) {
  1279. $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
  1280. } else if ($quickgrade) {
  1281. $comment = '<div id="com'.$auser->id.'">'
  1282. . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
  1283. . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
  1284. } else {
  1285. $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
  1286. }
  1287. } else {
  1288. $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
  1289. $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
  1290. $status = '<div id="st'.$auser->id.'">&nbsp;</div>';
  1291. if ($final_grade->locked or $final_grade->overridden) {
  1292. $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
  1293. $hassubmission = true;
  1294. } else if ($quickgrade) { // allow editing
  1295. $attributes = array();
  1296. $attributes['tabindex'] = $tabindex++;
  1297. $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
  1298. $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
  1299. $hassubmission = true;
  1300. } else {
  1301. $grade = '<div id="g'.$auser->id.'">-</div>';
  1302. }
  1303. if ($final_grade->locked or $final_grade->overridden) {
  1304. $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
  1305. } else if ($quickgrade) {
  1306. $comment = '<div id="com'.$auser->id.'">'
  1307. . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
  1308. . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
  1309. } else {
  1310. $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
  1311. }
  1312. }
  1313. if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
  1314. $auser->status = 0;
  1315. } else {
  1316. $auser->status = 1;
  1317. }
  1318. $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
  1319. if ($final_grade->locked or $final_grade->overridden) {
  1320. $buttontext = $strview;
  1321. }
  1322. ///No more buttons, we use popups ;-).
  1323. $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
  1324. . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;filter='.$filter.'&amp;offset='.$offset++;
  1325. $button = $OUTPUT->action_link($popup_url, $buttontext);
  1326. $status = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
  1327. $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
  1328. $outcomes = '';
  1329. if ($uses_outcomes) {
  1330. foreach($grading_info->outcomes as $n=>$outcome) {
  1331. $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
  1332. $options = make_grades_menu(-$outcome->scaleid);
  1333. if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
  1334. $options[0] = get_string('nooutcome', 'grades');
  1335. $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
  1336. } else {
  1337. $attributes = array();
  1338. $attributes['tabindex'] = $tabindex++;
  1339. $attributes['id'] = 'outcome_'.$n.'_'.$auser->id;
  1340. $outcomes .= ' '.html_writer::select($options, 'outcome_'.$n.'['.$auser->id.']', $outcome->grades[$auser->id]->grade, array(0=>get_string('nooutcome', 'grades')), $attributes);
  1341. }
  1342. $outcomes .= '</div>';
  1343. }
  1344. }
  1345. $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>';
  1346. $extradata = array();
  1347. foreach ($extrafields as $field) {
  1348. $extradata[] = $auser->{$field};
  1349. }
  1350. $row = array_merge(array($picture, $userlink), $extradata,
  1351. array($grade, $comment, $studentmodified, $teachermodified,
  1352. $status, $finalgrade));
  1353. if ($uses_outcomes) {
  1354. $row[] = $outcomes;
  1355. }
  1356. $table->add_data($row, $rowclass);
  1357. }
  1358. $currentposition++;
  1359. }
  1360. if ($hassubmission && ($this->assignment->assignmenttype=='upload' || $this->assignment->assignmenttype=='online' || $this->assignment->assignmenttype=='uploadsingle')) { //TODO: this is an ugly hack, where is the plugin spirit? (skodak)
  1361. echo html_writer::start_tag('div', array('class' => 'mod-assignment-download-link'));
  1362. echo html_writer::link(new moodle_url('/mod/assignment/submissions.php', array('id' => $this->cm->id, 'download' => 'zip')), get_string('downloadall', 'assignment'));
  1363. echo html_writer::end_tag('div');
  1364. }
  1365. $table->print_html(); /// Print the whole table
  1366. } else {
  1367. if ($filter == self::FILTER_SUBMITTED) {
  1368. echo html_writer::tag('div', get_string('nosubmisson', 'assignment'), array('class'=>'nosubmisson'));
  1369. } else if ($filter == self::FILTER_REQUIRE_GRADING) {
  1370. echo html_writer::tag('div', get_string('norequiregrading', 'assignment'), array('class'=>'norequiregrading'));
  1371. }
  1372. }
  1373. }
  1374. /// Print quickgrade form around the table
  1375. if ($quickgrade && $table->started_output && !empty($users)){
  1376. $mailinfopref = false;
  1377. if (get_user_preferences('assignment_mailinfo', 1)) {
  1378. $mailinfopref = true;
  1379. }
  1380. $emailnotification = html_writer::checkbox('mailinfo', 1, $mailinfopref, get_string('enablenotification','assignment'));
  1381. $emailnotification .= $OUTPUT->help_icon('enablenotification', 'assignment');
  1382. echo html_writer::tag('div', $emailnotification, array('class'=>'emailnotification'));
  1383. $savefeedback = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'fastg', 'value'=>get_string('saveallfeedback', 'assignment')));
  1384. echo html_writer::tag('div', $savefeedback, array('class'=>'fastgbutton'));
  1385. echo html_writer::end_tag('form');
  1386. } else if ($quickgrade) {
  1387. echo html_writer::end_tag('form');
  1388. }
  1389. echo '</div>';
  1390. /// End of fast grading form
  1391. /// Mini form for setting user preference
  1392. $formaction = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id));
  1393. $mform = new MoodleQuickForm('optionspref', 'post', $formaction, '', array('class'=>'optionspref'));
  1394. $mform->addElement('hidden', 'updatepref');
  1395. $mform->setDefault('updatepref', 1);
  1396. $mform->addElement('header', 'qgprefs', get_string('optionalsettings', 'assignment'));
  1397. $mform->addElement('select', 'filter', get_string('show'), $filters);
  1398. $mform->setDefault('filter', $filter);
  1399. $mform->addElement('text', 'perpage', get_string('pagesize', 'assignment'), array('size'=>1));
  1400. $mform->setDefault('perpage', $perpage);
  1401. if ($this->quickgrade_mode_allowed()) {
  1402. $mform->addElement('checkbox', 'quickgrade', get_string('quickgrade','assignment'));
  1403. $mform->setDefault('quickgrade', $quickgrade);
  1404. $mform->addHelpButton('quickgrade', 'quickgrade', 'assignment');
  1405. }
  1406. $mform->addElement('submit', 'savepreferences', get_string('savepreferences'));
  1407. $mform->display();
  1408. echo $OUTPUT->footer();
  1409. }
  1410. /**
  1411. * If the form was cancelled ('Cancel' or 'Next' was pressed), call cancel method
  1412. * from advanced grading (if applicable) and returns true
  1413. * If the form was submitted, validates it and returns false if validation did not pass.
  1414. * If validation passes, preprocess advanced grading (if applicable) and returns true.
  1415. *
  1416. * Note to the developers: This is NOT the correct way to implement advanced grading
  1417. * in grading form. The assignment grading was written long time ago and unfortunately
  1418. * does not fully use the mforms. Usually function is_validated() is called to
  1419. * validate the form and get_data() is called to get the data from the form.
  1420. *
  1421. * Here we have to push the calculated grade to $_POST['xgrade'] because further processing
  1422. * of the form gets the data not from form->get_data(), but from $_POST (using statement
  1423. * like $feedback = data_submitted() )
  1424. */
  1425. protected function validate_and_preprocess_feedback() {
  1426. global $USER, $CFG;
  1427. require_once($CFG->libdir.'/gradelib.php');
  1428. if (!($feedback = data_submitted()) || !isset($feedback->userid) || !isset($feedback->offset)) {
  1429. return true; // No incoming data, nothing to validate
  1430. }
  1431. $userid = required_param('userid', PARAM_INT);
  1432. $offset = required_param('offset', PARAM_INT);
  1433. $gradinginfo = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($userid));
  1434. $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
  1435. if ($gradingdisabled) {
  1436. return true;
  1437. }
  1438. $submissiondata = $this->display_submission($offset, $userid, false);
  1439. $mform = $submissiondata->mform;
  1440. $gradinginstance = $mform->use_advanced_grading();
  1441. if (optional_param('cancel', false, PARAM_BOOL) || optional_param('next', false, PARAM_BOOL)) {
  1442. // form was cancelled
  1443. if ($gradinginstance) {
  1444. $gradinginstance->cancel();
  1445. }
  1446. } else if ($mform->is_submitted()) {
  1447. // form was submitted (= a submit button other than 'cancel' or 'next' has been clicked)
  1448. if (!$mform->is_validated()) {
  1449. return false;
  1450. }
  1451. // preprocess advanced grading here
  1452. if ($gradinginstance) {
  1453. $data = $mform->get_data();
  1454. // create submission if it did not exist yet because we need submission->id for storing the grading instance
  1455. $submission = $this->get_submission($userid, true);
  1456. $_POST['xgrade'] = $gradinginstance->submit_and_get_grade($data->advancedgrading, $submission->id);
  1457. }
  1458. }
  1459. return true;
  1460. }
  1461. /**
  1462. * Process teacher feedback submission
  1463. *
  1464. * This is called by submissions() when a grading even has taken place.
  1465. * It gets its data from the submitted form.
  1466. *
  1467. * @global object
  1468. * @global object
  1469. * @global object
  1470. * @return object|bool The updated submission object or false
  1471. */
  1472. function process_feedback($formdata=null) {
  1473. global $CFG, $USER, $DB;
  1474. require_once($CFG->libdir.'/gradelib.php');
  1475. if (!$feedback = data_submitted() or !confirm_sesskey()) { // No incoming data?
  1476. return false;
  1477. }
  1478. ///For save and next, we need to know the userid to save, and the userid to go
  1479. ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
  1480. ///as the userid to store
  1481. if ((int)$feedback->saveuserid !== -1){
  1482. $feedback->userid = $feedback->saveuserid;
  1483. }
  1484. if (!empty($feedback->cancel)) { // User hit cancel button
  1485. return false;
  1486. }
  1487. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
  1488. // store outcomes if needed
  1489. $this->process_outcomes($feedback->userid);
  1490. $submission = $this->get_submission($feedback->userid, true); // Get or make one
  1491. if (!($grading_info->items[0]->grades[$feedback->userid]->locked ||
  1492. $grading_info->items[0]->grades[$feedback->userid]->overridden) ) {
  1493. $submission->grade = $feedback->xgrade;
  1494. $submission->submissioncomment = $feedback->submissioncomment_editor['text'];
  1495. $submission->teacher = $USER->id;
  1496. $mailinfo = get_user_preferences('assignment_mailinfo', 0);
  1497. if (!$mailinfo) {
  1498. $submission->mailed = 1; // treat as already mailed
  1499. } else {
  1500. $submission->mailed = 0; // Make sure mail goes out (again, even)
  1501. }
  1502. $submission->timemarked = time();
  1503. unset($submission->data1); // Don't need to update this.
  1504. unset($submission->data2); // Don't need to update this.
  1505. if (empty($submission->timemodified)) { // eg for offline assignments
  1506. // $submission->timemodified = time();
  1507. }
  1508. $DB->update_record('assignment_submissions', $submission);
  1509. // triger grade event
  1510. $this->update_grade($submission);
  1511. add_to_log($this->course->id, 'assignment', 'update grades',
  1512. 'submissions.php?id='.$this->cm->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
  1513. if (!is_null($formdata)) {
  1514. if ($this->type == 'upload' || $this->type == 'uploadsingle') {
  1515. $mformdata = $formdata->mform->get_data();
  1516. $mformdata = file_postupdate_standard_filemanager($mformdata, 'files', $formdata->fileui_options, $this->context, 'mod_assignment', 'response', $submission->id);
  1517. }
  1518. }
  1519. }
  1520. return $submission;
  1521. }
  1522. function process_outcomes($userid) {
  1523. global $CFG, $USER;
  1524. if (empty($CFG->enableoutcomes)) {
  1525. return;
  1526. }
  1527. require_once($CFG->libdir.'/gradelib.php');
  1528. if (!$formdata = data_submitted() or !confirm_sesskey()) {
  1529. return;
  1530. }
  1531. $data = array();
  1532. $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
  1533. if (!empty($grading_info->outcomes)) {
  1534. foreach($grading_info->outcomes as $n=>$old) {
  1535. $name = 'outcome_'.$n;
  1536. if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
  1537. $data[$n] = $formdata->{$name}[$userid];
  1538. }
  1539. }
  1540. }
  1541. if (count($data) > 0) {
  1542. grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
  1543. }
  1544. }
  1545. /**
  1546. * Load the submission object for a particular user
  1547. *
  1548. * @global object
  1549. * @global object
  1550. * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
  1551. * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
  1552. * @param bool $teachermodified student submission set if false
  1553. * @return object The submission
  1554. */
  1555. function get_submission($userid=0, $createnew=false, $teachermodified=false) {
  1556. global $USER, $DB;
  1557. if (empty($userid)) {
  1558. $userid = $USER->id;
  1559. }
  1560. $submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
  1561. if ($submission || !$createnew) {
  1562. return $submission;
  1563. }
  1564. $newsubmission = $this->prepare_new_submission($userid, $teachermodified);
  1565. $DB->insert_record("assignment_submissions", $newsubmission);
  1566. return $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
  1567. }
  1568. /**
  1569. * Check the given submission is complete. Preliminary rows are often created in the assignment_submissions
  1570. * table before a submission actually takes place. This function checks to see if the given submission has actually
  1571. * been submitted.
  1572. *
  1573. * @param stdClass $submission The submission we want to check for completion
  1574. * @return bool Indicates if the submission was found to be complete
  1575. */
  1576. public function is_submitted_with_required_data($submission) {
  1577. return $submission->timemodified;
  1578. }
  1579. /**
  1580. * Instantiates a new submission object for a given user
  1581. *
  1582. * Sets the assignment, userid and times, everything else is set to default values.
  1583. *
  1584. * @param int $userid The userid for which we want a submission object
  1585. * @param bool $teachermodified student submission set if false
  1586. * @return object The submission
  1587. */
  1588. function prepare_new_submission($userid, $teachermodified=false) {
  1589. $submission = new stdClass();
  1590. $submission->assignment = $this->assignment->id;
  1591. $submission->userid = $userid;
  1592. $submission->timecreated = time();
  1593. // teachers should not be modifying modified date, except offline assignments
  1594. if ($teachermodified) {
  1595. $submission->timemodified = 0;
  1596. } else {
  1597. $submission->timemodified = $submission->timecreated;
  1598. }
  1599. $submission->numfiles = 0;
  1600. $submission->data1 = '';
  1601. $submission->data2 = '';
  1602. $submission->grade = -1;
  1603. $submission->submissioncomment = '';
  1604. $submission->format = 0;
  1605. $submission->teacher = 0;
  1606. $submission->timemarked = 0;
  1607. $submission->mailed = 0;
  1608. return $submission;
  1609. }
  1610. /**
  1611. * Return all assignment submissions by ENROLLED students (even empty)
  1612. *
  1613. * @param string $sort optional field names for the ORDER BY in the sql query
  1614. * @param string $dir optional specifying the sort direction, defaults to DESC
  1615. * @return array The submission objects indexed by id
  1616. */
  1617. function get_submissions($sort='', $dir='DESC') {
  1618. return assignment_get_all_submissions($this->assignment, $sort, $dir);
  1619. }
  1620. /**
  1621. * Counts all complete (real) assignment submissions by enrolled students
  1622. *
  1623. * @param int $groupid (optional) If nonzero then count is restricted to this group
  1624. * @return int The number of submissions
  1625. */
  1626. function count_real_submissions($groupid=0) {
  1627. global $CFG;
  1628. global $DB;
  1629. // Grab the context assocated with our course module
  1630. $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
  1631. // Get ids of users enrolled in the given course.
  1632. list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
  1633. $params['assignmentid'] = $this->cm->instance;
  1634. // Get ids of users enrolled in the given course.
  1635. return $DB->count_records_sql("SELECT COUNT('x')
  1636. FROM {assignment_submissions} s
  1637. LEFT JOIN {assignment} a ON a.id = s.assignment
  1638. INNER JOIN ($enroledsql) u ON u.id = s.userid
  1639. WHERE s.assignment = :assignmentid AND
  1640. s.timemodified > 0", $params);
  1641. }
  1642. /**
  1643. * Alerts teachers by email of new or changed assignments that need grading
  1644. *
  1645. * First checks whether the option to email teachers is set for this assignment.
  1646. * Sends an email to ALL teachers in the course (or in the group if using separate groups).
  1647. * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
  1648. *
  1649. * @global object
  1650. * @global object
  1651. * @param $submission object The submission that has changed
  1652. * @return void
  1653. */
  1654. function email_teachers($submission) {
  1655. global $CFG, $DB;
  1656. if (empty($this->assignment->emailteachers)) { // No need to do anything
  1657. return;
  1658. }
  1659. $user = $DB->get_record('user', array('id'=>$submission->userid));
  1660. if ($teachers = $this->get_graders($user)) {
  1661. $strassignments = get_string('modulenameplural', 'assignment');
  1662. $strassignment = get_string('modulename', 'assignment');
  1663. $strsubmitted = get_string('submitted', 'assignment');
  1664. foreach ($teachers as $teacher) {
  1665. $info = new stdClass();
  1666. $info->username = fullname($user, true);
  1667. $info->assignment = format_string($this->assignment->name,true);
  1668. $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
  1669. $info->timeupdated = userdate($submission->timemodified, '%c', $teacher->timezone);
  1670. $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
  1671. $posttext = $this->email_teachers_text($info);
  1672. $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
  1673. $eventdata = new stdClass();
  1674. $eventdata->modulename = 'assignment';
  1675. $eventdata->userfrom = $user;
  1676. $eventdata->userto = $teacher;
  1677. $eventdata->subject = $postsubject;
  1678. $eventdata->fullmessage = $posttext;
  1679. $eventdata->fullmessageformat = FORMAT_PLAIN;
  1680. $eventdata->fullmessagehtml = $posthtml;
  1681. $eventdata->smallmessage = $postsubject;
  1682. $eventdata->name = 'assignment_updates';
  1683. $eventdata->component = 'mod_assignment';
  1684. $eventdata->notification = 1;
  1685. $eventdata->contexturl = $info->url;
  1686. $eventdata->contexturlname = $info->assignment;
  1687. message_send($eventdata);
  1688. }
  1689. }
  1690. }
  1691. /**
  1692. * Sends a file
  1693. *
  1694. * @param string $filearea
  1695. * @param array $args
  1696. * @param bool $forcedownload whether or not force download
  1697. * @param array $options additional options affecting the file serving
  1698. * @return bool
  1699. */
  1700. function send_file($filearea, $args, $forcedownload, array $options=array()) {
  1701. debugging('plugin does not implement file sending', DEBUG_DEVELOPER);
  1702. return false;
  1703. }
  1704. /**
  1705. * Returns a list of teachers that should be grading given submission
  1706. *
  1707. * @param object $user
  1708. * @return array
  1709. */
  1710. function get_graders($user) {
  1711. global $DB;
  1712. //potential graders
  1713. list($enrolledsql, $params) = get_enrolled_sql($this->context, 'mod/assignment:grade', 0, true);
  1714. $sql = "SELECT u.*
  1715. FROM {user} u
  1716. JOIN ($enrolledsql) je ON je.id = u.id";
  1717. $potgraders = $DB->get_records_sql($sql, $params);
  1718. $graders = array();
  1719. if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) { // Separate groups are being used
  1720. if ($groups = groups_get_all_groups($this->course->id, $user->id)) { // Try to find all groups
  1721. foreach ($groups as $group) {
  1722. foreach ($potgraders as $t) {
  1723. if ($t->id == $user->id) {
  1724. continue; // do not send self
  1725. }
  1726. if (groups_is_member($group->id, $t->id)) {
  1727. $graders[$t->id] = $t;
  1728. }
  1729. }
  1730. }
  1731. } else {
  1732. // user not in group, try to find graders without group
  1733. foreach ($potgraders as $t) {
  1734. if ($t->id == $user->id) {
  1735. continue; // do not send self
  1736. }
  1737. if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
  1738. $graders[$t->id] = $t;
  1739. }
  1740. }
  1741. }
  1742. } else {
  1743. foreach ($potgraders as $t) {
  1744. if ($t->id == $user->id) {
  1745. continue; // do not send self
  1746. }
  1747. $graders[$t->id] = $t;
  1748. }
  1749. }
  1750. return $graders;
  1751. }
  1752. /**
  1753. * Creates the text content for emails to teachers
  1754. *
  1755. * @param $info object The info used by the 'emailteachermail' language string
  1756. * @return string
  1757. */
  1758. function email_teachers_text($info) {
  1759. $posttext = format_string($this->course->shortname, true, array('context' => $this->coursecontext)).' -> '.
  1760. $this->strassignments.' -> '.
  1761. format_string($this->assignment->name, true, array('context' => $this->context))."\n";
  1762. $posttext .= '---------------------------------------------------------------------'."\n";
  1763. $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
  1764. $posttext .= "\n---------------------------------------------------------------------\n";
  1765. return $posttext;
  1766. }
  1767. /**
  1768. * Creates the html content for emails to teachers
  1769. *
  1770. * @param $info object The info used by the 'emailteachermailhtml' language string
  1771. * @return string
  1772. */
  1773. function email_teachers_html($info) {
  1774. global $CFG;
  1775. $posthtml = '<p><font face="sans-serif">'.
  1776. '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname, true, array('context' => $this->coursecontext)).'</a> ->'.
  1777. '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
  1778. '<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>';
  1779. $posthtml .= '<hr /><font face="sans-serif">';
  1780. $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
  1781. $posthtml .= '</font><hr />';
  1782. return $posthtml;
  1783. }
  1784. /**
  1785. * Produces a list of links to the files uploaded by a user
  1786. *
  1787. * @param $userid int optional id of the user. If 0 then $USER->id is used.
  1788. * @param $return boolean optional defaults to false. If true the list is returned rather than printed
  1789. * @return string optional
  1790. */
  1791. function print_user_files($userid=0, $return=false) {
  1792. global $CFG, $USER, $OUTPUT;
  1793. if (!$userid) {
  1794. if (!isloggedin()) {
  1795. return '';
  1796. }
  1797. $userid = $USER->id;
  1798. }
  1799. $output = '';
  1800. $submission = $this->get_submission($userid);
  1801. if (!$submission) {
  1802. return $output;
  1803. }
  1804. $fs = get_file_storage();
  1805. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
  1806. if (!empty($files)) {
  1807. require_once($CFG->dirroot . '/mod/assignment/locallib.php');
  1808. if ($CFG->enableportfolios) {
  1809. require_once($CFG->libdir.'/portfoliolib.php');
  1810. $button = new portfolio_add_button();
  1811. }
  1812. foreach ($files as $file) {
  1813. $filename = $file->get_filename();
  1814. $mimetype = $file->get_mimetype();
  1815. $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
  1816. $output .= '<a href="'.$path.'" >'.$OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon')).s($filename).'</a>';
  1817. if ($CFG->enableportfolios && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
  1818. $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
  1819. $button->set_format_by_file($file);
  1820. $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
  1821. }
  1822. if ($CFG->enableplagiarism) {
  1823. require_once($CFG->libdir.'/plagiarismlib.php');
  1824. $output .= plagiarism_get_links(array('userid'=>$userid, 'file'=>$file, 'cmid'=>$this->cm->id, 'course'=>$this->course, 'assignment'=>$this->assignment));
  1825. $output .= '<br />';
  1826. }
  1827. }
  1828. if ($CFG->enableportfolios && count($files) > 1 && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
  1829. $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id), '/mod/assignment/locallib.php');
  1830. $output .= '<br />' . $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
  1831. }
  1832. }
  1833. $output = '<div class="files">'.$output.'</div>';
  1834. if ($return) {
  1835. return $output;
  1836. }
  1837. echo $output;
  1838. }
  1839. /**
  1840. * Count the files uploaded by a given user
  1841. *
  1842. * @param $itemid int The submission's id as the file's itemid.
  1843. * @return int
  1844. */
  1845. function count_user_files($itemid) {
  1846. $fs = get_file_storage();
  1847. $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $itemid, "id", false);
  1848. return count($files);
  1849. }
  1850. /**
  1851. * Returns true if the student is allowed to submit
  1852. *
  1853. * Checks that the assignment has started and, if the option to prevent late
  1854. * submissions is set, also checks that the assignment has not yet closed.
  1855. * @return boolean
  1856. */
  1857. function isopen() {
  1858. $time = time();
  1859. if ($this->assignment->preventlate && $this->assignment->timedue) {
  1860. return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
  1861. } else {
  1862. return ($this->assignment->timeavailable <= $time);
  1863. }
  1864. }
  1865. /**
  1866. * Return true if is set description is hidden till available date
  1867. *
  1868. * This is needed by calendar so that hidden descriptions do not
  1869. * come up in upcoming events.
  1870. *
  1871. * Check that description is hidden till available date
  1872. * By default return false
  1873. * Assignments types should implement this method if needed
  1874. * @return boolen
  1875. */
  1876. function description_is_hidden() {
  1877. return false;
  1878. }
  1879. /**
  1880. * Return an outline of the user's interaction with the assignment
  1881. *
  1882. * The default method prints the grade and timemodified
  1883. * @param $grade object
  1884. * @return object with properties ->info and ->time
  1885. */
  1886. function user_outline($grade) {
  1887. $result = new stdClass();
  1888. $result->info = get_string('grade').': '.$grade->str_long_grade;
  1889. $result->time = $grade->dategraded;
  1890. return $result;
  1891. }
  1892. /**
  1893. * Print complete information about the user's interaction with the assignment
  1894. *
  1895. * @param $user object
  1896. */
  1897. function user_complete($user, $grade=null) {
  1898. global $OUTPUT;
  1899. if ($submission = $this->get_submission($user->id)) {
  1900. $fs = get_file_storage();
  1901. if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
  1902. $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
  1903. foreach ($files as $file) {
  1904. $countfiles .= "; ".$file->get_filename();
  1905. }
  1906. }
  1907. echo $OUTPUT->box_start();
  1908. echo get_string("lastmodified").": ";
  1909. echo userdate($submission->timemodified);
  1910. echo $this->display_lateness($submission->timemodified);
  1911. $this->print_user_files($user->id);
  1912. echo '<br />';
  1913. $this->view_feedback($submission);
  1914. echo $OUTPUT->box_end();
  1915. } else {
  1916. if ($grade) {
  1917. echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
  1918. if ($grade->str_feedback) {
  1919. echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
  1920. }
  1921. }
  1922. print_string("notsubmittedyet", "assignment");
  1923. }
  1924. }
  1925. /**
  1926. * Return a string indicating how late a submission is
  1927. *
  1928. * @param $timesubmitted int
  1929. * @return string
  1930. */
  1931. function display_lateness($timesubmitted) {
  1932. return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
  1933. }
  1934. /**
  1935. * Empty method stub for all delete actions.
  1936. */
  1937. function delete() {
  1938. //nothing by default
  1939. redirect('view.php?id='.$this->cm->id);
  1940. }
  1941. /**
  1942. * Empty custom feedback grading form.
  1943. */
  1944. function custom_feedbackform($submission, $return=false) {
  1945. //nothing by default
  1946. return '';
  1947. }
  1948. /**
  1949. * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
  1950. * for the course (see resource).
  1951. *
  1952. * Given a course_module object, this function returns any "extra" information that may be needed
  1953. * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
  1954. *
  1955. * @param $coursemodule object The coursemodule object (record).
  1956. * @return cached_cm_info Object used to customise appearance on course page
  1957. */
  1958. function get_coursemodule_info($coursemodule) {
  1959. return null;
  1960. }
  1961. /**
  1962. * Plugin cron method - do not use $this here, create new assignment instances if needed.
  1963. * @return void
  1964. */
  1965. function cron() {
  1966. //no plugin cron by default - override if needed
  1967. }
  1968. /**
  1969. * Reset all submissions
  1970. */
  1971. function reset_userdata($data) {
  1972. global $CFG, $DB;
  1973. if (!$DB->count_records('assignment', array('course'=>$data->courseid, 'assignmenttype'=>$this->type))) {
  1974. return array(); // no assignments of this type present
  1975. }
  1976. $componentstr = get_string('modulenameplural', 'assignment');
  1977. $status = array();
  1978. $typestr = get_string('type'.$this->type, 'assignment');
  1979. // ugly hack to support pluggable assignment type titles...
  1980. if($typestr === '[[type'.$this->type.']]'){
  1981. $typestr = get_string('type'.$this->type, 'assignment_'.$this->type);
  1982. }
  1983. if (!empty($data->reset_assignment_submissions)) {
  1984. $assignmentssql = "SELECT a.id
  1985. FROM {assignment} a
  1986. WHERE a.course=? AND a.assignmenttype=?";
  1987. $params = array($data->courseid, $this->type);
  1988. // now get rid of all submissions and responses
  1989. $fs = get_file_storage();
  1990. if ($assignments = $DB->get_records_sql($assignmentssql, $params)) {
  1991. foreach ($assignments as $assignmentid=>$unused) {
  1992. if (!$cm = get_coursemodule_from_instance('assignment', $assignmentid)) {
  1993. continue;
  1994. }
  1995. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  1996. $fs->delete_area_files($context->id, 'mod_assignment', 'submission');
  1997. $fs->delete_area_files($context->id, 'mod_assignment', 'response');
  1998. }
  1999. }
  2000. $DB->delete_records_select('assignment_submissions', "assignment IN ($assignmentssql)", $params);
  2001. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallsubmissions','assignment').': '.$typestr, 'error'=>false);
  2002. if (empty($data->reset_gradebook_grades)) {
  2003. // remove all grades from gradebook
  2004. assignment_reset_gradebook($data->courseid, $this->type);
  2005. }
  2006. }
  2007. /// updating dates - shift may be negative too
  2008. if ($data->timeshift) {
  2009. shift_course_mod_dates('assignment', array('timedue', 'timeavailable'), $data->timeshift, $data->courseid);
  2010. $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged').': '.$typestr, 'error'=>false);
  2011. }
  2012. return $status;
  2013. }
  2014. function portfolio_exportable() {
  2015. return false;
  2016. }
  2017. /**
  2018. * base implementation for backing up subtype specific information
  2019. * for one single module
  2020. *
  2021. * @param filehandle $bf file handle for xml file to write to
  2022. * @param mixed $preferences the complete backup preference object
  2023. *
  2024. * @return boolean
  2025. *
  2026. * @static
  2027. */
  2028. static function backup_one_mod($bf, $preferences, $assignment) {
  2029. return true;
  2030. }
  2031. /**
  2032. * base implementation for backing up subtype specific information
  2033. * for one single submission
  2034. *
  2035. * @param filehandle $bf file handle for xml file to write to
  2036. * @param mixed $preferences the complete backup preference object
  2037. * @param object $submission the assignment submission db record
  2038. *
  2039. * @return boolean
  2040. *
  2041. * @static
  2042. */
  2043. static function backup_one_submission($bf, $preferences, $assignment, $submission) {
  2044. return true;
  2045. }
  2046. /**
  2047. * base implementation for restoring subtype specific information
  2048. * for one single module
  2049. *
  2050. * @param array $info the array representing the xml
  2051. * @param object $restore the restore preferences
  2052. *
  2053. * @return boolean
  2054. *
  2055. * @static
  2056. */
  2057. static function restore_one_mod($info, $restore, $assignment) {
  2058. return true;
  2059. }
  2060. /**
  2061. * base implementation for restoring subtype specific information
  2062. * for one single submission
  2063. *
  2064. * @param object $submission the newly created submission
  2065. * @param array $info the array representing the xml
  2066. * @param object $restore the restore preferences
  2067. *
  2068. * @return boolean
  2069. *
  2070. * @static
  2071. */
  2072. static function restore_one_submission($info, $restore, $assignment, $submission) {
  2073. return true;
  2074. }
  2075. } ////// End of the assignment_base class
  2076. class assignment_grading_form extends moodleform {
  2077. /** @var stores the advaned grading instance (if used in grading) */
  2078. private $advancegradinginstance;
  2079. function definition() {
  2080. global $OUTPUT;
  2081. $mform =& $this->_form;
  2082. if (isset($this->_customdata->advancedgradinginstance)) {
  2083. $this->use_advanced_grading($this->_customdata->advancedgradinginstance);
  2084. }
  2085. $formattr = $mform->getAttributes();
  2086. $formattr['id'] = 'submitform';
  2087. $mform->setAttributes($formattr);
  2088. // hidden params
  2089. $mform->addElement('hidden', 'offset', ($this->_customdata->offset+1));
  2090. $mform->setType('offset', PARAM_INT);
  2091. $mform->addElement('hidden', 'userid', $this->_customdata->userid);
  2092. $mform->setType('userid', PARAM_INT);
  2093. $mform->addElement('hidden', 'nextid', $this->_customdata->nextid);
  2094. $mform->setType('nextid', PARAM_INT);
  2095. $mform->addElement('hidden', 'id', $this->_customdata->cm->id);
  2096. $mform->setType('id', PARAM_INT);
  2097. $mform->addElement('hidden', 'sesskey', sesskey());
  2098. $mform->setType('sesskey', PARAM_ALPHANUM);
  2099. $mform->addElement('hidden', 'mode', 'grade');
  2100. $mform->setType('mode', PARAM_TEXT);
  2101. $mform->addElement('hidden', 'menuindex', "0");
  2102. $mform->setType('menuindex', PARAM_INT);
  2103. $mform->addElement('hidden', 'saveuserid', "-1");
  2104. $mform->setType('saveuserid', PARAM_INT);
  2105. $mform->addElement('hidden', 'filter', "0");
  2106. $mform->setType('filter', PARAM_INT);
  2107. $mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->user),
  2108. fullname($this->_customdata->user, true) . '<br/>' .
  2109. userdate($this->_customdata->submission->timemodified) .
  2110. $this->_customdata->lateness );
  2111. $this->add_submission_content();
  2112. $this->add_grades_section();
  2113. $this->add_feedback_section();
  2114. if ($this->_customdata->submission->timemarked) {
  2115. $datestring = userdate($this->_customdata->submission->timemarked)."&nbsp; (".format_time(time() - $this->_customdata->submission->timemarked).")";
  2116. $mform->addElement('header', 'Last Grade', get_string('lastgrade', 'assignment'));
  2117. $mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->teacher) ,
  2118. fullname($this->_customdata->teacher,true).
  2119. '<br/>'.$datestring);
  2120. }
  2121. // buttons
  2122. $this->add_action_buttons();
  2123. }
  2124. /**
  2125. * Gets or sets the instance for advanced grading
  2126. *
  2127. * @param gradingform_instance $gradinginstance
  2128. */
  2129. public function use_advanced_grading($gradinginstance = false) {
  2130. if ($gradinginstance !== false) {
  2131. $this->advancegradinginstance = $gradinginstance;
  2132. }
  2133. return $this->advancegradinginstance;
  2134. }
  2135. /**
  2136. * Add the grades configuration section to the assignment configuration form
  2137. */
  2138. function add_grades_section() {
  2139. global $CFG;
  2140. $mform =& $this->_form;
  2141. $attributes = array();
  2142. if ($this->_customdata->gradingdisabled) {
  2143. $attributes['disabled'] ='disabled';
  2144. }
  2145. $mform->addElement('header', 'Grades', get_string('grades', 'grades'));
  2146. $grademenu = make_grades_menu($this->_customdata->assignment->grade);
  2147. if ($gradinginstance = $this->use_advanced_grading()) {
  2148. $gradinginstance->get_controller()->set_grade_range($grademenu);
  2149. $gradingelement = $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
  2150. if ($this->_customdata->gradingdisabled) {
  2151. $gradingelement->freeze();
  2152. } else {
  2153. $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
  2154. }
  2155. } else {
  2156. // use simple direct grading
  2157. $grademenu['-1'] = get_string('nograde');
  2158. $mform->addElement('select', 'xgrade', get_string('grade').':', $grademenu, $attributes);
  2159. $mform->setDefault('xgrade', $this->_customdata->submission->grade ); //@fixme some bug when element called 'grade' makes it break
  2160. $mform->setType('xgrade', PARAM_INT);
  2161. }
  2162. if (!empty($this->_customdata->enableoutcomes)) {
  2163. foreach($this->_customdata->grading_info->outcomes as $n=>$outcome) {
  2164. $options = make_grades_menu(-$outcome->scaleid);
  2165. if ($outcome->grades[$this->_customdata->submission->userid]->locked) {
  2166. $options[0] = get_string('nooutcome', 'grades');
  2167. $mform->addElement('static', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':',
  2168. $options[$outcome->grades[$this->_customdata->submission->userid]->grade]);
  2169. } else {
  2170. $options[''] = get_string('nooutcome', 'grades');
  2171. $attributes = array('id' => 'menuoutcome_'.$n );
  2172. $mform->addElement('select', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':', $options, $attributes );
  2173. $mform->setType('outcome_'.$n.'['.$this->_customdata->userid.']', PARAM_INT);
  2174. $mform->setDefault('outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->grades[$this->_customdata->submission->userid]->grade );
  2175. }
  2176. }
  2177. }
  2178. $course_context = get_context_instance(CONTEXT_MODULE , $this->_customdata->cm->id);
  2179. if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
  2180. $grade = '<a href="'.$CFG->wwwroot.'/grade/report/grader/index.php?id='. $this->_customdata->courseid .'" >'.
  2181. $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade . '</a>';
  2182. }else{
  2183. $grade = $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade;
  2184. }
  2185. $mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assignment').':' ,$grade);
  2186. $mform->setType('finalgrade', PARAM_INT);
  2187. }
  2188. /**
  2189. *
  2190. * @global core_renderer $OUTPUT
  2191. */
  2192. function add_feedback_section() {
  2193. global $OUTPUT;
  2194. $mform =& $this->_form;
  2195. $mform->addElement('header', 'Feed Back', get_string('feedback', 'grades'));
  2196. if ($this->_customdata->gradingdisabled) {
  2197. $mform->addElement('static', 'disabledfeedback', $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_feedback );
  2198. } else {
  2199. // visible elements
  2200. $mform->addElement('editor', 'submissioncomment_editor', get_string('feedback', 'assignment').':', null, $this->get_editor_options() );
  2201. $mform->setType('submissioncomment_editor', PARAM_RAW); // to be cleaned before display
  2202. $mform->setDefault('submissioncomment_editor', $this->_customdata->submission->submissioncomment);
  2203. //$mform->addRule('submissioncomment', get_string('required'), 'required', null, 'client');
  2204. switch ($this->_customdata->assignment->assignmenttype) {
  2205. case 'upload' :
  2206. case 'uploadsingle' :
  2207. $mform->addElement('filemanager', 'files_filemanager', get_string('responsefiles', 'assignment'). ':', null, $this->_customdata->fileui_options);
  2208. break;
  2209. default :
  2210. break;
  2211. }
  2212. $mform->addElement('hidden', 'mailinfo_h', "0");
  2213. $mform->setType('mailinfo_h', PARAM_INT);
  2214. $mform->addElement('checkbox', 'mailinfo',get_string('enablenotification','assignment').
  2215. $OUTPUT->help_icon('enablenotification', 'assignment') .':' );
  2216. $mform->setType('mailinfo', PARAM_INT);
  2217. }
  2218. }
  2219. function add_action_buttons($cancel = true, $submitlabel = NULL) {
  2220. $mform =& $this->_form;
  2221. //if there are more to be graded.
  2222. if ($this->_customdata->nextid>0) {
  2223. $buttonarray=array();
  2224. $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
  2225. //@todo: fix accessibility: javascript dependency not necessary
  2226. $buttonarray[] = &$mform->createElement('submit', 'saveandnext', get_string('saveandnext'));
  2227. $buttonarray[] = &$mform->createElement('submit', 'next', get_string('next'));
  2228. $buttonarray[] = &$mform->createElement('cancel');
  2229. } else {
  2230. $buttonarray=array();
  2231. $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
  2232. $buttonarray[] = &$mform->createElement('cancel');
  2233. }
  2234. $mform->addGroup($buttonarray, 'grading_buttonar', '', array(' '), false);
  2235. $mform->closeHeaderBefore('grading_buttonar');
  2236. $mform->setType('grading_buttonar', PARAM_RAW);
  2237. }
  2238. function add_submission_content() {
  2239. $mform =& $this->_form;
  2240. $mform->addElement('header', 'Submission', get_string('submission', 'assignment'));
  2241. $mform->addElement('static', '', '' , $this->_customdata->submission_content );
  2242. }
  2243. protected function get_editor_options() {
  2244. $editoroptions = array();
  2245. $editoroptions['component'] = 'mod_assignment';
  2246. $editoroptions['filearea'] = 'feedback';
  2247. $editoroptions['noclean'] = false;
  2248. $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)
  2249. $editoroptions['maxbytes'] = $this->_customdata->maxbytes;
  2250. $editoroptions['context'] = $this->_customdata->context;
  2251. return $editoroptions;
  2252. }
  2253. public function set_data($data) {
  2254. $editoroptions = $this->get_editor_options();
  2255. if (!isset($data->text)) {
  2256. $data->text = '';
  2257. }
  2258. if (!isset($data->format)) {
  2259. $data->textformat = FORMAT_HTML;
  2260. } else {
  2261. $data->textformat = $data->format;
  2262. }
  2263. if (!empty($this->_customdata->submission->id)) {
  2264. $itemid = $this->_customdata->submission->id;
  2265. } else {
  2266. $itemid = null;
  2267. }
  2268. switch ($this->_customdata->assignment->assignmenttype) {
  2269. case 'upload' :
  2270. case 'uploadsingle' :
  2271. $data = file_prepare_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
  2272. break;
  2273. default :
  2274. break;
  2275. }
  2276. $data = file_prepare_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
  2277. return parent::set_data($data);
  2278. }
  2279. public function get_data() {
  2280. $data = parent::get_data();
  2281. if (!empty($this->_customdata->submission->id)) {
  2282. $itemid = $this->_customdata->submission->id;
  2283. } else {
  2284. $itemid = null; //TODO: this is wrong, itemid MUST be known when saving files!! (skodak)
  2285. }
  2286. if ($data) {
  2287. $editoroptions = $this->get_editor_options();
  2288. switch ($this->_customdata->assignment->assignmenttype) {
  2289. case 'upload' :
  2290. case 'uploadsingle' :
  2291. $data = file_postupdate_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
  2292. break;
  2293. default :
  2294. break;
  2295. }
  2296. $data = file_postupdate_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
  2297. }
  2298. if ($this->use_advanced_grading() && !isset($data->advancedgrading)) {
  2299. $data->advancedgrading = null;
  2300. }
  2301. return $data;
  2302. }
  2303. }
  2304. /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
  2305. /**
  2306. * Deletes an assignment instance
  2307. *
  2308. * This is done by calling the delete_instance() method of the assignment type class
  2309. */
  2310. function assignment_delete_instance($id){
  2311. global $CFG, $DB;
  2312. if (! $assignment = $DB->get_record('assignment', array('id'=>$id))) {
  2313. return false;
  2314. }
  2315. // fall back to base class if plugin missing
  2316. $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
  2317. if (file_exists($classfile)) {
  2318. require_once($classfile);
  2319. $assignmentclass = "assignment_$assignment->assignmenttype";
  2320. } else {
  2321. debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
  2322. $assignmentclass = "assignment_base";
  2323. }
  2324. $ass = new $assignmentclass();
  2325. return $ass->delete_instance($assignment);
  2326. }
  2327. /**
  2328. * Updates an assignment instance
  2329. *
  2330. * This is done by calling the update_instance() method of the assignment type class
  2331. */
  2332. function assignment_update_instance($assignment){
  2333. global $CFG;
  2334. $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
  2335. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2336. $assignmentclass = "assignment_$assignment->assignmenttype";
  2337. $ass = new $assignmentclass();
  2338. return $ass->update_instance($assignment);
  2339. }
  2340. /**
  2341. * Adds an assignment instance
  2342. *
  2343. * This is done by calling the add_instance() method of the assignment type class
  2344. *
  2345. * @param stdClass $assignment
  2346. * @param mod_assignment_mod_form $mform
  2347. * @return int intance id
  2348. */
  2349. function assignment_add_instance($assignment, $mform = null) {
  2350. global $CFG;
  2351. $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
  2352. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2353. $assignmentclass = "assignment_$assignment->assignmenttype";
  2354. $ass = new $assignmentclass();
  2355. return $ass->add_instance($assignment);
  2356. }
  2357. /**
  2358. * Returns an outline of a user interaction with an assignment
  2359. *
  2360. * This is done by calling the user_outline() method of the assignment type class
  2361. */
  2362. function assignment_user_outline($course, $user, $mod, $assignment) {
  2363. global $CFG;
  2364. require_once("$CFG->libdir/gradelib.php");
  2365. require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
  2366. $assignmentclass = "assignment_$assignment->assignmenttype";
  2367. $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
  2368. $grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
  2369. if (!empty($grades->items[0]->grades)) {
  2370. return $ass->user_outline(reset($grades->items[0]->grades));
  2371. } else {
  2372. return null;
  2373. }
  2374. }
  2375. /**
  2376. * Prints the complete info about a user's interaction with an assignment
  2377. *
  2378. * This is done by calling the user_complete() method of the assignment type class
  2379. */
  2380. function assignment_user_complete($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. $grade = false;
  2389. } else {
  2390. $grade = reset($grades->items[0]->grades);
  2391. }
  2392. return $ass->user_complete($user, $grade);
  2393. }
  2394. /**
  2395. * Function to be run periodically according to the moodle cron
  2396. *
  2397. * Finds all assignment notifications that have yet to be mailed out, and mails them
  2398. */
  2399. function assignment_cron () {
  2400. global $CFG, $USER, $DB;
  2401. /// first execute all crons in plugins
  2402. if ($plugins = get_plugin_list('assignment')) {
  2403. foreach ($plugins as $plugin=>$dir) {
  2404. require_once("$dir/assignment.class.php");
  2405. $assignmentclass = "assignment_$plugin";
  2406. $ass = new $assignmentclass();
  2407. $ass->cron();
  2408. }
  2409. }
  2410. /// Notices older than 1 day will not be mailed. This is to avoid the problem where
  2411. /// cron has not been running for a long time, and then suddenly people are flooded
  2412. /// with mail from the past few weeks or months
  2413. $timenow = time();
  2414. $endtime = $timenow - $CFG->maxeditingtime;
  2415. $starttime = $endtime - 24 * 3600; /// One day earlier
  2416. if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
  2417. $realuser = clone($USER);
  2418. foreach ($submissions as $key => $submission) {
  2419. $DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id));
  2420. }
  2421. $timenow = time();
  2422. foreach ($submissions as $submission) {
  2423. echo "Processing assignment submission $submission->id\n";
  2424. if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) {
  2425. echo "Could not find user $user->id\n";
  2426. continue;
  2427. }
  2428. if (! $course = $DB->get_record("course", array("id"=>$submission->course))) {
  2429. echo "Could not find course $submission->course\n";
  2430. continue;
  2431. }
  2432. /// Override the language and timezone of the "current" user, so that
  2433. /// mail is customised for the receiver.
  2434. cron_setup_user($user, $course);
  2435. $coursecontext = get_context_instance(CONTEXT_COURSE, $submission->course);
  2436. $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
  2437. if (!is_enrolled($coursecontext, $user->id)) {
  2438. echo fullname($user)." not an active participant in " . $courseshortname . "\n";
  2439. continue;
  2440. }
  2441. if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) {
  2442. echo "Could not find teacher $submission->teacher\n";
  2443. continue;
  2444. }
  2445. if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
  2446. echo "Could not find course module for assignment id $submission->assignment\n";
  2447. continue;
  2448. }
  2449. if (! $mod->visible) { /// Hold mail notification for hidden assignments until later
  2450. continue;
  2451. }
  2452. $strassignments = get_string("modulenameplural", "assignment");
  2453. $strassignment = get_string("modulename", "assignment");
  2454. $assignmentinfo = new stdClass();
  2455. $assignmentinfo->teacher = fullname($teacher);
  2456. $assignmentinfo->assignment = format_string($submission->name,true);
  2457. $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
  2458. $postsubject = "$courseshortname: $strassignments: ".format_string($submission->name,true);
  2459. $posttext = "$courseshortname -> $strassignments -> ".format_string($submission->name,true)."\n";
  2460. $posttext .= "---------------------------------------------------------------------\n";
  2461. $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
  2462. $posttext .= "---------------------------------------------------------------------\n";
  2463. if ($user->mailformat == 1) { // HTML
  2464. $posthtml = "<p><font face=\"sans-serif\">".
  2465. "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$courseshortname</a> ->".
  2466. "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
  2467. "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
  2468. $posthtml .= "<hr /><font face=\"sans-serif\">";
  2469. $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
  2470. $posthtml .= "</font><hr />";
  2471. } else {
  2472. $posthtml = "";
  2473. }
  2474. $eventdata = new stdClass();
  2475. $eventdata->modulename = 'assignment';
  2476. $eventdata->userfrom = $teacher;
  2477. $eventdata->userto = $user;
  2478. $eventdata->subject = $postsubject;
  2479. $eventdata->fullmessage = $posttext;
  2480. $eventdata->fullmessageformat = FORMAT_PLAIN;
  2481. $eventdata->fullmessagehtml = $posthtml;
  2482. $eventdata->smallmessage = get_string('assignmentmailsmall', 'assignment', $assignmentinfo);
  2483. $eventdata->name = 'assignment_updates';
  2484. $eventdata->component = 'mod_assignment';
  2485. $eventdata->notification = 1;
  2486. $eventdata->contexturl = $assignmentinfo->url;
  2487. $eventdata->contexturlname = $assignmentinfo->assignment;
  2488. message_send($eventdata);
  2489. }
  2490. cron_setup_user();
  2491. }
  2492. return true;
  2493. }
  2494. /**
  2495. * Return grade for given user or all users.
  2496. *
  2497. * @param stdClass $assignment An assignment instance
  2498. * @param int $userid Optional user id, 0 means all users
  2499. * @return array An array of grades, false if none
  2500. */
  2501. function assignment_get_user_grades($assignment, $userid=0) {
  2502. global $CFG, $DB;
  2503. if ($userid) {
  2504. $user = "AND u.id = :userid";
  2505. $params = array('userid'=>$userid);
  2506. } else {
  2507. $user = "";
  2508. }
  2509. $params['aid'] = $assignment->id;
  2510. $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
  2511. s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
  2512. FROM {user} u, {assignment_submissions} s
  2513. WHERE u.id = s.userid AND s.assignment = :aid
  2514. $user";
  2515. return $DB->get_records_sql($sql, $params);
  2516. }
  2517. /**
  2518. * Update activity grades
  2519. *
  2520. * @category grade
  2521. * @param stdClass $assignment Assignment instance
  2522. * @param int $userid specific user only, 0 means all
  2523. * @param bool $nullifnone Not used
  2524. */
  2525. function assignment_update_grades($assignment, $userid=0, $nullifnone=true) {
  2526. global $CFG, $DB;
  2527. require_once($CFG->libdir.'/gradelib.php');
  2528. if ($assignment->grade == 0) {
  2529. assignment_grade_item_update($assignment);
  2530. } else if ($grades = assignment_get_user_grades($assignment, $userid)) {
  2531. foreach($grades as $k=>$v) {
  2532. if ($v->rawgrade == -1) {
  2533. $grades[$k]->rawgrade = null;
  2534. }
  2535. }
  2536. assignment_grade_item_update($assignment, $grades);
  2537. } else {
  2538. assignment_grade_item_update($assignment);
  2539. }
  2540. }
  2541. /**
  2542. * Update all grades in gradebook.
  2543. */
  2544. function assignment_upgrade_grades() {
  2545. global $DB;
  2546. $sql = "SELECT COUNT('x')
  2547. FROM {assignment} a, {course_modules} cm, {modules} m
  2548. WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
  2549. $count = $DB->count_records_sql($sql);
  2550. $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
  2551. FROM {assignment} a, {course_modules} cm, {modules} m
  2552. WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
  2553. $rs = $DB->get_recordset_sql($sql);
  2554. if ($rs->valid()) {
  2555. // too much debug output
  2556. $pbar = new progress_bar('assignmentupgradegrades', 500, true);
  2557. $i=0;
  2558. foreach ($rs as $assignment) {
  2559. $i++;
  2560. upgrade_set_timeout(60*5); // set up timeout, may also abort execution
  2561. assignment_update_grades($assignment);
  2562. $pbar->update($i, $count, "Updating Assignment grades ($i/$count).");
  2563. }
  2564. upgrade_set_timeout(); // reset to default timeout
  2565. }
  2566. $rs->close();
  2567. }
  2568. /**
  2569. * Create grade item for given assignment
  2570. *
  2571. * @category grade
  2572. * @param stdClass $assignment An assignment instance with extra cmidnumber property
  2573. * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
  2574. * @return int 0 if ok, error code otherwise
  2575. */
  2576. function assignment_grade_item_update($assignment, $grades=NULL) {
  2577. global $CFG;
  2578. require_once($CFG->libdir.'/gradelib.php');
  2579. if (!isset($assignment->courseid)) {
  2580. $assignment->courseid = $assignment->course;
  2581. }
  2582. $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
  2583. if ($assignment->grade > 0) {
  2584. $params['gradetype'] = GRADE_TYPE_VALUE;
  2585. $params['grademax'] = $assignment->grade;
  2586. $params['grademin'] = 0;
  2587. } else if ($assignment->grade < 0) {
  2588. $params['gradetype'] = GRADE_TYPE_SCALE;
  2589. $params['scaleid'] = -$assignment->grade;
  2590. } else {
  2591. $params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only
  2592. }
  2593. if ($grades === 'reset') {
  2594. $params['reset'] = true;
  2595. $grades = NULL;
  2596. }
  2597. return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
  2598. }
  2599. /**
  2600. * Delete grade item for given assignment
  2601. *
  2602. * @category grade
  2603. * @param object $assignment object
  2604. * @return object assignment
  2605. */
  2606. function assignment_grade_item_delete($assignment) {
  2607. global $CFG;
  2608. require_once($CFG->libdir.'/gradelib.php');
  2609. if (!isset($assignment->courseid)) {
  2610. $assignment->courseid = $assignment->course;
  2611. }
  2612. return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
  2613. }
  2614. /**
  2615. * Serves assignment submissions and other files.
  2616. *
  2617. * @package mod_assignment
  2618. * @category files
  2619. * @param stdClass $course course object
  2620. * @param stdClass $cm course module object
  2621. * @param stdClass $context context object
  2622. * @param string $filearea file area
  2623. * @param array $args extra arguments
  2624. * @param bool $forcedownload whether or not force download
  2625. * @param array $options additional options affecting the file serving
  2626. * @return bool false if file not found, does not return if found - just send the file
  2627. */
  2628. function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
  2629. global $CFG, $DB;
  2630. if ($context->contextlevel != CONTEXT_MODULE) {
  2631. return false;
  2632. }
  2633. require_login($course, false, $cm);
  2634. if (!$assignment = $DB->get_record('assignment', array('id'=>$cm->instance))) {
  2635. return false;
  2636. }
  2637. require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
  2638. $assignmentclass = 'assignment_'.$assignment->assignmenttype;
  2639. $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
  2640. return $assignmentinstance->send_file($filearea, $args, $forcedownload, $options);
  2641. }
  2642. /**
  2643. * Checks if a scale is being used by an assignment
  2644. *
  2645. * This is used by the backup code to decide whether to back up a scale
  2646. * @param $assignmentid int
  2647. * @param $scaleid int
  2648. * @return boolean True if the scale is used by the assignment
  2649. */
  2650. function assignment_scale_used($assignmentid, $scaleid) {
  2651. global $DB;
  2652. $return = false;
  2653. $rec = $DB->get_record('assignment', array('id'=>$assignmentid,'grade'=>-$scaleid));
  2654. if (!empty($rec) && !empty($scaleid)) {
  2655. $return = true;
  2656. }
  2657. return $return;
  2658. }
  2659. /**
  2660. * Checks if scale is being used by any instance of assignment
  2661. *
  2662. * This is used to find out if scale used anywhere
  2663. * @param $scaleid int
  2664. * @return boolean True if the scale is used by any assignment
  2665. */
  2666. function assignment_scale_used_anywhere($scaleid) {
  2667. global $DB;
  2668. if ($scaleid and $DB->record_exists('assignment', array('grade'=>-$scaleid))) {
  2669. return true;
  2670. } else {
  2671. return false;
  2672. }
  2673. }
  2674. /**
  2675. * Make sure up-to-date events are created for all assignment instances
  2676. *
  2677. * This standard function will check all instances of this module
  2678. * and make sure there are up-to-date events created for each of them.
  2679. * If courseid = 0, then every assignment event in the site is checked, else
  2680. * only assignment events belonging to the course specified are checked.
  2681. * This function is used, in its new format, by restore_refresh_events()
  2682. *
  2683. * @param $courseid int optional If zero then all assignments for all courses are covered
  2684. * @return boolean Always returns true
  2685. */
  2686. function assignment_refresh_events($courseid = 0) {
  2687. global $DB;
  2688. if ($courseid == 0) {
  2689. if (! $assignments = $DB->get_records("assignment")) {
  2690. return true;
  2691. }
  2692. } else {
  2693. if (! $assignments = $DB->get_records("assignment", array("course"=>$courseid))) {
  2694. return true;
  2695. }
  2696. }
  2697. $moduleid = $DB->get_field('modules', 'id', array('name'=>'assignment'));
  2698. foreach ($assignments as $assignment) {
  2699. $cm = get_coursemodule_from_id('assignment', $assignment->id);
  2700. $event = new stdClass();
  2701. $event->name = $assignment->name;
  2702. $event->description = format_module_intro('assignment', $assignment, $cm->id);
  2703. $event->timestart = $assignment->timedue;
  2704. if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
  2705. update_event($event);
  2706. } else {
  2707. $event->courseid = $assignment->course;
  2708. $event->groupid = 0;
  2709. $event->userid = 0;
  2710. $event->modulename = 'assignment';
  2711. $event->instance = $assignment->id;
  2712. $event->eventtype = 'due';
  2713. $event->timeduration = 0;
  2714. $event->visible = $DB->get_field('course_modules', 'visible', array('module'=>$moduleid, 'instance'=>$assignment->id));
  2715. add_event($event);
  2716. }
  2717. }
  2718. return true;
  2719. }
  2720. /**
  2721. * Print recent activity from all assignments in a given course
  2722. *
  2723. * This is used by the recent activity block
  2724. */
  2725. function assignment_print_recent_activity($course, $viewfullnames, $timestart) {
  2726. global $CFG, $USER, $DB, $OUTPUT;
  2727. // do not use log table if possible, it may be huge
  2728. if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, asb.userid,
  2729. u.firstname, u.lastname, u.email, u.picture
  2730. FROM {assignment_submissions} asb
  2731. JOIN {assignment} a ON a.id = asb.assignment
  2732. JOIN {course_modules} cm ON cm.instance = a.id
  2733. JOIN {modules} md ON md.id = cm.module
  2734. JOIN {user} u ON u.id = asb.userid
  2735. WHERE asb.timemodified > ? AND
  2736. a.course = ? AND
  2737. md.name = 'assignment'
  2738. ORDER BY asb.timemodified ASC", array($timestart, $course->id))) {
  2739. return false;
  2740. }
  2741. $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
  2742. $show = array();
  2743. $grader = array();
  2744. foreach($submissions as $submission) {
  2745. if (!array_key_exists($submission->cmid, $modinfo->cms)) {
  2746. continue;
  2747. }
  2748. $cm = $modinfo->cms[$submission->cmid];
  2749. if (!$cm->uservisible) {
  2750. continue;
  2751. }
  2752. if ($submission->userid == $USER->id) {
  2753. $show[] = $submission;
  2754. continue;
  2755. }
  2756. // the act of sumbitting of assignment may be considered private - only graders will see it if specified
  2757. if (empty($CFG->assignment_showrecentsubmissions)) {
  2758. if (!array_key_exists($cm->id, $grader)) {
  2759. $grader[$cm->id] = has_capability('moodle/grade:viewall', get_context_instance(CONTEXT_MODULE, $cm->id));
  2760. }
  2761. if (!$grader[$cm->id]) {
  2762. continue;
  2763. }
  2764. }
  2765. $groupmode = groups_get_activity_groupmode($cm, $course);
  2766. if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
  2767. if (isguestuser()) {
  2768. // shortcut - guest user does not belong into any group
  2769. continue;
  2770. }
  2771. if (is_null($modinfo->groups)) {
  2772. $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
  2773. }
  2774. // this will be slow - show only users that share group with me in this cm
  2775. if (empty($modinfo->groups[$cm->id])) {
  2776. continue;
  2777. }
  2778. $usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
  2779. if (is_array($usersgroups)) {
  2780. $usersgroups = array_keys($usersgroups);
  2781. $intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
  2782. if (empty($intersect)) {
  2783. continue;
  2784. }
  2785. }
  2786. }
  2787. $show[] = $submission;
  2788. }
  2789. if (empty($show)) {
  2790. return false;
  2791. }
  2792. echo $OUTPUT->heading(get_string('newsubmissions', 'assignment').':', 3);
  2793. foreach ($show as $submission) {
  2794. $cm = $modinfo->cms[$submission->cmid];
  2795. $link = $CFG->wwwroot.'/mod/assignment/view.php?id='.$cm->id;
  2796. print_recent_activity_note($submission->timemodified, $submission, $cm->name, $link, false, $viewfullnames);
  2797. }
  2798. return true;
  2799. }
  2800. /**
  2801. * Returns all assignments since a given time in specified forum.
  2802. */
  2803. function assignment_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
  2804. global $CFG, $COURSE, $USER, $DB;
  2805. if ($COURSE->id == $courseid) {
  2806. $course = $COURSE;
  2807. } else {
  2808. $course = $DB->get_record('course', array('id'=>$courseid));
  2809. }
  2810. $modinfo = get_fast_modinfo($course);
  2811. $cm = $modinfo->cms[$cmid];
  2812. $params = array();
  2813. if ($userid) {
  2814. $userselect = "AND u.id = :userid";
  2815. $params['userid'] = $userid;
  2816. } else {
  2817. $userselect = "";
  2818. }
  2819. if ($groupid) {
  2820. $groupselect = "AND gm.groupid = :groupid";
  2821. $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
  2822. $params['groupid'] = $groupid;
  2823. } else {
  2824. $groupselect = "";
  2825. $groupjoin = "";
  2826. }
  2827. $params['cminstance'] = $cm->instance;
  2828. $params['timestart'] = $timestart;
  2829. $userfields = user_picture::fields('u', null, 'userid');
  2830. if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified,
  2831. $userfields
  2832. FROM {assignment_submissions} asb
  2833. JOIN {assignment} a ON a.id = asb.assignment
  2834. JOIN {user} u ON u.id = asb.userid
  2835. $groupjoin
  2836. WHERE asb.timemodified > :timestart AND a.id = :cminstance
  2837. $userselect $groupselect
  2838. ORDER BY asb.timemodified ASC", $params)) {
  2839. return;
  2840. }
  2841. $groupmode = groups_get_activity_groupmode($cm, $course);
  2842. $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
  2843. $grader = has_capability('moodle/grade:viewall', $cm_context);
  2844. $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
  2845. $viewfullnames = has_capability('moodle/site:viewfullnames', $cm_context);
  2846. if (is_null($modinfo->groups)) {
  2847. $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
  2848. }
  2849. $show = array();
  2850. foreach($submissions as $submission) {
  2851. if ($submission->userid == $USER->id) {
  2852. $show[] = $submission;
  2853. continue;
  2854. }
  2855. // the act of submitting of assignment may be considered private - only graders will see it if specified
  2856. if (empty($CFG->assignment_showrecentsubmissions)) {
  2857. if (!$grader) {
  2858. continue;
  2859. }
  2860. }
  2861. if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
  2862. if (isguestuser()) {
  2863. // shortcut - guest user does not belong into any group
  2864. continue;
  2865. }
  2866. // this will be slow - show only users that share group with me in this cm
  2867. if (empty($modinfo->groups[$cm->id])) {
  2868. continue;
  2869. }
  2870. $usersgroups = groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
  2871. if (is_array($usersgroups)) {
  2872. $usersgroups = array_keys($usersgroups);
  2873. $intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
  2874. if (empty($intersect)) {
  2875. continue;
  2876. }
  2877. }
  2878. }
  2879. $show[] = $submission;
  2880. }
  2881. if (empty($show)) {
  2882. return;
  2883. }
  2884. if ($grader) {
  2885. require_once($CFG->libdir.'/gradelib.php');
  2886. $userids = array();
  2887. foreach ($show as $id=>$submission) {
  2888. $userids[] = $submission->userid;
  2889. }
  2890. $grades = grade_get_grades($courseid, 'mod', 'assignment', $cm->instance, $userids);
  2891. }
  2892. $aname = format_string($cm->name,true);
  2893. foreach ($show as $submission) {
  2894. $tmpactivity = new stdClass();
  2895. $tmpactivity->type = 'assignment';
  2896. $tmpactivity->cmid = $cm->id;
  2897. $tmpactivity->name = $aname;
  2898. $tmpactivity->sectionnum = $cm->sectionnum;
  2899. $tmpactivity->timestamp = $submission->timemodified;
  2900. if ($grader) {
  2901. $tmpactivity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
  2902. }
  2903. $userfields = explode(',', user_picture::fields());
  2904. foreach ($userfields as $userfield) {
  2905. if ($userfield == 'id') {
  2906. $tmpactivity->user->{$userfield} = $submission->userid; // aliased in SQL above
  2907. } else {
  2908. $tmpactivity->user->{$userfield} = $submission->{$userfield};
  2909. }
  2910. }
  2911. $tmpactivity->user->fullname = fullname($submission, $viewfullnames);
  2912. $activities[$index++] = $tmpactivity;
  2913. }
  2914. return;
  2915. }
  2916. /**
  2917. * Print recent activity from all assignments in a given course
  2918. *
  2919. * This is used by course/recent.php
  2920. */
  2921. function assignment_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
  2922. global $CFG, $OUTPUT;
  2923. echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
  2924. echo "<tr><td class=\"userpicture\" valign=\"top\">";
  2925. echo $OUTPUT->user_picture($activity->user);
  2926. echo "</td><td>";
  2927. if ($detail) {
  2928. $modname = $modnames[$activity->type];
  2929. echo '<div class="title">';
  2930. echo "<img src=\"" . $OUTPUT->pix_url('icon', 'assignment') . "\" ".
  2931. "class=\"icon\" alt=\"$modname\">";
  2932. echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id={$activity->cmid}\">{$activity->name}</a>";
  2933. echo '</div>';
  2934. }
  2935. if (isset($activity->grade)) {
  2936. echo '<div class="grade">';
  2937. echo get_string('grade').': ';
  2938. echo $activity->grade;
  2939. echo '</div>';
  2940. }
  2941. echo '<div class="user">';
  2942. echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
  2943. ."{$activity->user->fullname}</a> - ".userdate($activity->timestamp);
  2944. echo '</div>';
  2945. echo "</td></tr></table>";
  2946. }
  2947. /// GENERIC SQL FUNCTIONS
  2948. /**
  2949. * Fetch info from logs
  2950. *
  2951. * @param $log object with properties ->info (the assignment id) and ->userid
  2952. * @return array with assignment name and user firstname and lastname
  2953. */
  2954. function assignment_log_info($log) {
  2955. global $CFG, $DB;
  2956. return $DB->get_record_sql("SELECT a.name, u.firstname, u.lastname
  2957. FROM {assignment} a, {user} u
  2958. WHERE a.id = ? AND u.id = ?", array($log->info, $log->userid));
  2959. }
  2960. /**
  2961. * Return list of marked submissions that have not been mailed out for currently enrolled students
  2962. *
  2963. * @return array
  2964. */
  2965. function assignment_get_unmailed_submissions($starttime, $endtime) {
  2966. global $CFG, $DB;
  2967. return $DB->get_records_sql("SELECT s.*, a.course, a.name
  2968. FROM {assignment_submissions} s,
  2969. {assignment} a
  2970. WHERE s.mailed = 0
  2971. AND s.timemarked <= ?
  2972. AND s.timemarked >= ?
  2973. AND s.assignment = a.id", array($endtime, $starttime));
  2974. }
  2975. /**
  2976. * Counts all complete (real) assignment submissions by enrolled students for the given course modeule.
  2977. *
  2978. * @deprecated Since Moodle 2.2 MDL-abc - Please do not use this function any more.
  2979. * @param cm_info $cm The course module that we wish to perform the count on.
  2980. * @param int $groupid (optional) If nonzero then count is restricted to this group
  2981. * @return int The number of submissions
  2982. */
  2983. function assignment_count_real_submissions($cm, $groupid=0) {
  2984. global $CFG, $DB;
  2985. // Grab the assignment type for the given course module
  2986. $assignmenttype = $DB->get_field($cm->modname, 'assignmenttype', array('id' => $cm->instance), MUST_EXIST);
  2987. // Create the expected class file path and class name for the returned assignemnt type
  2988. $filename = "{$CFG->dirroot}/mod/assignment/type/{$assignmenttype}/assignment.class.php";
  2989. $classname = "assignment_{$assignmenttype}";
  2990. // If the file exists and the class is not already loaded we require the class file
  2991. if (file_exists($filename) && !class_exists($classname)) {
  2992. require_once($filename);
  2993. }
  2994. // If the required class is still not loaded then we revert to assignment base
  2995. if (!class_exists($classname)) {
  2996. $classname = 'assignment_base';
  2997. }
  2998. $instance = new $classname;
  2999. // Attach the course module to the assignment type instance and then call the method for counting submissions
  3000. $instance->cm = $cm;
  3001. return $instance->count_real_submissions($groupid);
  3002. }
  3003. /**
  3004. * Return all assignment submissions by ENROLLED students (even empty)
  3005. *
  3006. * There are also assignment type methods get_submissions() wich in the default
  3007. * implementation simply call this function.
  3008. * @param $sort string optional field names for the ORDER BY in the sql query
  3009. * @param $dir string optional specifying the sort direction, defaults to DESC
  3010. * @return array The submission objects indexed by id
  3011. */
  3012. function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
  3013. /// Return all assignment submissions by ENROLLED students (even empty)
  3014. global $CFG, $DB;
  3015. if ($sort == "lastname" or $sort == "firstname") {
  3016. $sort = "u.$sort $dir";
  3017. } else if (empty($sort)) {
  3018. $sort = "a.timemodified DESC";
  3019. } else {
  3020. $sort = "a.$sort $dir";
  3021. }
  3022. /* not sure this is needed at all since assignment already has a course define, so this join?
  3023. $select = "s.course = '$assignment->course' AND";
  3024. if ($assignment->course == SITEID) {
  3025. $select = '';
  3026. }*/
  3027. return $DB->get_records_sql("SELECT a.*
  3028. FROM {assignment_submissions} a, {user} u
  3029. WHERE u.id = a.userid
  3030. AND a.assignment = ?
  3031. ORDER BY $sort", array($assignment->id));
  3032. }
  3033. /**
  3034. * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
  3035. * for the course (see resource).
  3036. *
  3037. * Given a course_module object, this function returns any "extra" information that may be needed
  3038. * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
  3039. *
  3040. * @param $coursemodule object The coursemodule object (record).
  3041. * @return cached_cm_info An object on information that the courses will know about (most noticeably, an icon).
  3042. */
  3043. function assignment_get_coursemodule_info($coursemodule) {
  3044. global $CFG, $DB;
  3045. if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance),
  3046. 'id, assignmenttype, name, intro, introformat')) {
  3047. return false;
  3048. }
  3049. $libfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
  3050. if (file_exists($libfile)) {
  3051. require_once($libfile);
  3052. $assignmentclass = "assignment_$assignment->assignmenttype";
  3053. $ass = new $assignmentclass('staticonly');
  3054. if (!($result = $ass->get_coursemodule_info($coursemodule))) {
  3055. $result = new cached_cm_info();
  3056. $result->name = $assignment->name;
  3057. }
  3058. if ($coursemodule->showdescription) {
  3059. // Convert intro to html. Do not filter cached version, filters run at display time.
  3060. $result->content = format_module_intro('assignment', $assignment, $coursemodule->id, false);
  3061. }
  3062. return $result;
  3063. } else {
  3064. debugging('Incorrect assignment type: '.$assignment->assignmenttype);
  3065. return false;
  3066. }
  3067. }
  3068. /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
  3069. /**
  3070. * Returns an array of installed assignment types indexed and sorted by name
  3071. *
  3072. * @return array The index is the name of the assignment type, the value its full name from the language strings
  3073. */
  3074. function assignment_types() {
  3075. $types = array();
  3076. $names = get_plugin_list('assignment');
  3077. foreach ($names as $name=>$dir) {
  3078. $types[$name] = get_string('type'.$name, 'assignment');
  3079. // ugly hack to support pluggable assignment type titles..
  3080. if ($types[$name] == '[[type'.$name.']]') {
  3081. $types[$name] = get_string('type'.$name, 'assignment_'.$name);
  3082. }
  3083. }
  3084. asort($types);
  3085. return $types;
  3086. }
  3087. function assignment_print_overview($courses, &$htmlarray) {
  3088. global $USER, $CFG, $DB;
  3089. require_once($CFG->libdir.'/gradelib.php');
  3090. if (empty($courses) || !is_array($courses) || count($courses) == 0) {
  3091. return array();
  3092. }
  3093. if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
  3094. return;
  3095. }
  3096. $assignmentids = array();
  3097. // Do assignment_base::isopen() here without loading the whole thing for speed
  3098. foreach ($assignments as $key => $assignment) {
  3099. $time = time();
  3100. if ($assignment->timedue) {
  3101. if ($assignment->preventlate) {
  3102. $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
  3103. } else {
  3104. $isopen = ($assignment->timeavailable <= $time);
  3105. }
  3106. }
  3107. if (empty($isopen) || empty($assignment->timedue)) {
  3108. unset($assignments[$key]);
  3109. } else {
  3110. $assignmentids[] = $assignment->id;
  3111. }
  3112. }
  3113. if (empty($assignmentids)){
  3114. // no assignments to look at - we're done
  3115. return true;
  3116. }
  3117. $strduedate = get_string('duedate', 'assignment');
  3118. $strduedateno = get_string('duedateno', 'assignment');
  3119. $strgraded = get_string('graded', 'assignment');
  3120. $strnotgradedyet = get_string('notgradedyet', 'assignment');
  3121. $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
  3122. $strsubmitted = get_string('submitted', 'assignment');
  3123. $strassignment = get_string('modulename', 'assignment');
  3124. $strreviewed = get_string('reviewed','assignment');
  3125. // NOTE: we do all possible database work here *outside* of the loop to ensure this scales
  3126. //
  3127. list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
  3128. // build up and array of unmarked submissions indexed by assignment id/ userid
  3129. // for use where the user has grading rights on assignment
  3130. $rs = $DB->get_recordset_sql("SELECT id, assignment, userid
  3131. FROM {assignment_submissions}
  3132. WHERE teacher = 0 AND timemarked = 0
  3133. AND assignment $sqlassignmentids", $assignmentidparams);
  3134. $unmarkedsubmissions = array();
  3135. foreach ($rs as $rd) {
  3136. $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
  3137. }
  3138. $rs->close();
  3139. // get all user submissions, indexed by assignment id
  3140. $mysubmissions = $DB->get_records_sql("SELECT assignment, timemarked, teacher, grade
  3141. FROM {assignment_submissions}
  3142. WHERE userid = ? AND
  3143. assignment $sqlassignmentids", array_merge(array($USER->id), $assignmentidparams));
  3144. foreach ($assignments as $assignment) {
  3145. $grading_info = grade_get_grades($assignment->course, 'mod', 'assignment', $assignment->id, $USER->id);
  3146. $final_grade = $grading_info->items[0]->grades[$USER->id];
  3147. $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
  3148. '<a '.($assignment->visible ? '':' class="dimmed"').
  3149. 'title="'.$strassignment.'" href="'.$CFG->wwwroot.
  3150. '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
  3151. $assignment->name.'</a></div>';
  3152. if ($assignment->timedue) {
  3153. $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
  3154. } else {
  3155. $str .= '<div class="info">'.$strduedateno.'</div>';
  3156. }
  3157. $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
  3158. if (has_capability('mod/assignment:grade', $context)) {
  3159. // count how many people can submit
  3160. $submissions = 0; // init
  3161. if ($students = get_enrolled_users($context, 'mod/assignment:view', 0, 'u.id')) {
  3162. foreach ($students as $student) {
  3163. if (isset($unmarkedsubmissions[$assignment->id][$student->id])) {
  3164. $submissions++;
  3165. }
  3166. }
  3167. }
  3168. if ($submissions) {
  3169. $link = new moodle_url('/mod/assignment/submissions.php', array('id'=>$assignment->coursemodule));
  3170. $str .= '<div class="details"><a href="'.$link.'">'.get_string('submissionsnotgraded', 'assignment', $submissions).'</a></div>';
  3171. }
  3172. } else {
  3173. $str .= '<div class="details">';
  3174. if (isset($mysubmissions[$assignment->id])) {
  3175. $submission = $mysubmissions[$assignment->id];
  3176. if ($submission->teacher == 0 && $submission->timemarked == 0 && !$final_grade->grade) {
  3177. $str .= $strsubmitted . ', ' . $strnotgradedyet;
  3178. } else if ($submission->grade <= 0 && !$final_grade->grade) {
  3179. $str .= $strsubmitted . ', ' . $strreviewed;
  3180. } else {
  3181. $str .= $strsubmitted . ', ' . $strgraded;
  3182. }
  3183. } else {
  3184. $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
  3185. }
  3186. $str .= '</div>';
  3187. }
  3188. $str .= '</div>';
  3189. if (empty($htmlarray[$assignment->course]['assignment'])) {
  3190. $htmlarray[$assignment->course]['assignment'] = $str;
  3191. } else {
  3192. $htmlarray[$assignment->course]['assignment'] .= $str;
  3193. }
  3194. }
  3195. }
  3196. function assignment_display_lateness($timesubmitted, $timedue) {
  3197. if (!$timedue) {
  3198. return '';
  3199. }
  3200. $time = $timedue - $timesubmitted;
  3201. if ($time < 0) {
  3202. $timetext = get_string('late', 'assignment', format_time($time));
  3203. return ' (<span class="late">'.$timetext.'</span>)';
  3204. } else {
  3205. $timetext = get_string('early', 'assignment', format_time($time));
  3206. return ' (<span class="early">'.$timetext.'</span>)';
  3207. }
  3208. }
  3209. function assignment_get_view_actions() {
  3210. return array('view');
  3211. }
  3212. function assignment_get_post_actions() {
  3213. return array('upload');
  3214. }
  3215. function assignment_get_types() {
  3216. global $CFG;
  3217. $types = array();
  3218. $type = new stdClass();
  3219. $type->modclass = MOD_CLASS_ACTIVITY;
  3220. $type->type = "assignment_group_start";
  3221. $type->typestr = '--'.get_string('modulenameplural', 'assignment');
  3222. $types[] = $type;
  3223. $standardassignments = array('upload','online','uploadsingle','offline');
  3224. foreach ($standardassignments as $assignmenttype) {
  3225. $type = new stdClass();
  3226. $type->modclass = MOD_CLASS_ACTIVITY;
  3227. $type->type = "assignment&amp;type=$assignmenttype";
  3228. $type->typestr = get_string("type$assignmenttype", 'assignment');
  3229. $types[] = $type;
  3230. }
  3231. /// Drop-in extra assignment types
  3232. $assignmenttypes = get_list_of_plugins('mod/assignment/type');
  3233. foreach ($assignmenttypes as $assignmenttype) {
  3234. if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) { // Not wanted
  3235. continue;
  3236. }
  3237. if (!in_array($assignmenttype, $standardassignments)) {
  3238. $type = new stdClass();
  3239. $type->modclass = MOD_CLASS_ACTIVITY;
  3240. $type->type = "assignment&amp;type=$assignmenttype";
  3241. $type->typestr = get_string("type$assignmenttype", 'assignment_'.$assignmenttype);
  3242. $types[] = $type;
  3243. }
  3244. }
  3245. $type = new stdClass();
  3246. $type->modclass = MOD_CLASS_ACTIVITY;
  3247. $type->type = "assignment_group_end";
  3248. $type->typestr = '--';
  3249. $types[] = $type;
  3250. return $types;
  3251. }
  3252. /**
  3253. * Removes all grades from gradebook
  3254. *
  3255. * @param int $courseid The ID of the course to reset
  3256. * @param string $type Optional type of assignment to limit the reset to a particular assignment type
  3257. */
  3258. function assignment_reset_gradebook($courseid, $type='') {
  3259. global $CFG, $DB;
  3260. $params = array('courseid'=>$courseid);
  3261. if ($type) {
  3262. $type = "AND a.assignmenttype= :type";
  3263. $params['type'] = $type;
  3264. }
  3265. $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
  3266. FROM {assignment} a, {course_modules} cm, {modules} m
  3267. WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid $type";
  3268. if ($assignments = $DB->get_records_sql($sql, $params)) {
  3269. foreach ($assignments as $assignment) {
  3270. assignment_grade_item_update($assignment, 'reset');
  3271. }
  3272. }
  3273. }
  3274. /**
  3275. * This function is used by the reset_course_userdata function in moodlelib.
  3276. * This function will remove all posts from the specified assignment
  3277. * and clean up any related data.
  3278. * @param $data the data submitted from the reset course.
  3279. * @return array status array
  3280. */
  3281. function assignment_reset_userdata($data) {
  3282. global $CFG;
  3283. $status = array();
  3284. foreach (get_plugin_list('assignment') as $type=>$dir) {
  3285. require_once("$dir/assignment.class.php");
  3286. $assignmentclass = "assignment_$type";
  3287. $ass = new $assignmentclass();
  3288. $status = array_merge($status, $ass->reset_userdata($data));
  3289. }
  3290. return $status;
  3291. }
  3292. /**
  3293. * Implementation of the function for printing the form elements that control
  3294. * whether the course reset functionality affects the assignment.
  3295. * @param $mform form passed by reference
  3296. */
  3297. function assignment_reset_course_form_definition(&$mform) {
  3298. $mform->addElement('header', 'assignmentheader', get_string('modulenameplural', 'assignment'));
  3299. $mform->addElement('advcheckbox', 'reset_assignment_submissions', get_string('deleteallsubmissions','assignment'));
  3300. }
  3301. /**
  3302. * Course reset form defaults.
  3303. */
  3304. function assignment_reset_course_form_defaults($course) {
  3305. return array('reset_assignment_submissions'=>1);
  3306. }
  3307. /**
  3308. * Returns all other caps used in module
  3309. */
  3310. function assignment_get_extra_capabilities() {
  3311. return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/grade:managegradingforms');
  3312. }
  3313. /**
  3314. * @param string $feature FEATURE_xx constant for requested feature
  3315. * @return mixed True if module supports feature, null if doesn't know
  3316. */
  3317. function assignment_supports($feature) {
  3318. switch($feature) {
  3319. case FEATURE_GROUPS: return true;
  3320. case FEATURE_GROUPINGS: return true;
  3321. case FEATURE_GROUPMEMBERSONLY: return true;
  3322. case FEATURE_MOD_INTRO: return true;
  3323. case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
  3324. case FEATURE_GRADE_HAS_GRADE: return true;
  3325. case FEATURE_GRADE_OUTCOMES: return true;
  3326. case FEATURE_GRADE_HAS_GRADE: return true;
  3327. case FEATURE_BACKUP_MOODLE2: return true;
  3328. case FEATURE_SHOW_DESCRIPTION: return true;
  3329. case FEATURE_ADVANCED_GRADING: return true;
  3330. default: return null;
  3331. }
  3332. }
  3333. /**
  3334. * Adds module specific settings to the settings block
  3335. *
  3336. * @param settings_navigation $settings The settings navigation object
  3337. * @param navigation_node $assignmentnode The node to add module settings to
  3338. */
  3339. function assignment_extend_settings_navigation(settings_navigation $settings, navigation_node $assignmentnode) {
  3340. global $PAGE, $DB, $USER, $CFG;
  3341. $assignmentrow = $DB->get_record("assignment", array("id" => $PAGE->cm->instance));
  3342. require_once "$CFG->dirroot/mod/assignment/type/$assignmentrow->assignmenttype/assignment.class.php";
  3343. $assignmentclass = 'assignment_'.$assignmentrow->assignmenttype;
  3344. $assignmentinstance = new $assignmentclass($PAGE->cm->id, $assignmentrow, $PAGE->cm, $PAGE->course);
  3345. $allgroups = false;
  3346. // Add assignment submission information
  3347. if (has_capability('mod/assignment:grade', $PAGE->cm->context)) {
  3348. if ($allgroups && has_capability('moodle/site:accessallgroups', $PAGE->cm->context)) {
  3349. $group = 0;
  3350. } else {
  3351. $group = groups_get_activity_group($PAGE->cm);
  3352. }
  3353. $link = new moodle_url('/mod/assignment/submissions.php', array('id'=>$PAGE->cm->id));
  3354. if ($assignmentrow->assignmenttype == 'offline') {
  3355. $string = get_string('viewfeedback', 'assignment');
  3356. } else if ($count = $assignmentinstance->count_real_submissions($group)) {
  3357. $string = get_string('viewsubmissions', 'assignment', $count);
  3358. } else {
  3359. $string = get_string('noattempts', 'assignment');
  3360. }
  3361. $assignmentnode->add($string, $link, navigation_node::TYPE_SETTING);
  3362. }
  3363. if (is_object($assignmentinstance) && method_exists($assignmentinstance, 'extend_settings_navigation')) {
  3364. $assignmentinstance->extend_settings_navigation($assignmentnode);
  3365. }
  3366. }
  3367. /**
  3368. * generate zip file from array of given files
  3369. * @param array $filesforzipping - array of files to pass into archive_to_pathname
  3370. * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
  3371. */
  3372. function assignment_pack_files($filesforzipping) {
  3373. global $CFG;
  3374. //create path for new zip file.
  3375. $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
  3376. //zip files
  3377. $zipper = new zip_packer();
  3378. if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
  3379. return $tempzip;
  3380. }
  3381. return false;
  3382. }
  3383. /**
  3384. * Lists all file areas current user may browse
  3385. *
  3386. * @package mod_assignment
  3387. * @category files
  3388. * @param stdClass $course course object
  3389. * @param stdClass $cm course module object
  3390. * @param stdClass $context context object
  3391. * @return array available file areas
  3392. */
  3393. function assignment_get_file_areas($course, $cm, $context) {
  3394. $areas = array();
  3395. if (has_capability('moodle/course:managefiles', $context)) {
  3396. $areas['submission'] = get_string('assignmentsubmission', 'assignment');
  3397. }
  3398. return $areas;
  3399. }
  3400. /**
  3401. * File browsing support for assignment module.
  3402. *
  3403. * @param file_browser $browser
  3404. * @param array $areas
  3405. * @param stdClass $course
  3406. * @param cm_info $cm
  3407. * @param context $context
  3408. * @param string $filearea
  3409. * @param int $itemid
  3410. * @param string $filepath
  3411. * @param string $filename
  3412. * @return file_info_stored file_info_stored instance or null if not found
  3413. */
  3414. function assignment_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
  3415. global $CFG, $DB, $USER;
  3416. if ($context->contextlevel != CONTEXT_MODULE || $filearea != 'submission') {
  3417. return null;
  3418. }
  3419. if (!$submission = $DB->get_record('assignment_submissions', array('id' => $itemid))) {
  3420. return null;
  3421. }
  3422. if (!(($submission->userid == $USER->id && has_capability('mod/assignment:view', $context))
  3423. || has_capability('mod/assignment:grade', $context))) {
  3424. // no permission to view this submission
  3425. return null;
  3426. }
  3427. $fs = get_file_storage();
  3428. $filepath = is_null($filepath) ? '/' : $filepath;
  3429. $filename = is_null($filename) ? '.' : $filename;
  3430. if (!($storedfile = $fs->get_file($context->id, 'mod_assignment', $filearea, $itemid, $filepath, $filename))) {
  3431. return null;
  3432. }
  3433. $urlbase = $CFG->wwwroot.'/pluginfile.php';
  3434. return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false);
  3435. }
  3436. /**
  3437. * Return a list of page types
  3438. * @param string $pagetype current page type
  3439. * @param stdClass $parentcontext Block's parent context
  3440. * @param stdClass $currentcontext Current context of block
  3441. */
  3442. function assignment_page_type_list($pagetype, $parentcontext, $currentcontext) {
  3443. $module_pagetype = array(
  3444. 'mod-assignment-*'=>get_string('page-mod-assignment-x', 'assignment'),
  3445. 'mod-assignment-view'=>get_string('page-mod-assignment-view', 'assignment'),
  3446. 'mod-assignment-submissions'=>get_string('page-mod-assignment-submissions', 'assignment')
  3447. );
  3448. return $module_pagetype;
  3449. }
  3450. /**
  3451. * Lists all gradable areas for the advanced grading methods gramework
  3452. *
  3453. * @return array
  3454. */
  3455. function assignment_grading_areas_list() {
  3456. return array('submission' => get_string('submissions', 'mod_assignment'));
  3457. }