PageRenderTime 72ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/assign/locallib.php

https://bitbucket.org/andrewdavidson/sl-clone
PHP | 3377 lines | 2037 code | 481 blank | 859 comment | 430 complexity | 52364399fde9e76d730c2878f3c9b36e MD5 | raw file
Possible License(s): AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, Apache-2.0, GPL-3.0, BSD-3-Clause, LGPL-2.1
  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. * This file contains the definition for the class assignment
  18. *
  19. * This class provides all the functionality for the new assign module.
  20. *
  21. * @package mod_assign
  22. * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24. */
  25. defined('MOODLE_INTERNAL') || die();
  26. /**
  27. * Assignment submission statuses
  28. */
  29. define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft'); // student thinks it is a draft
  30. define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
  31. /**
  32. * Search filters for grading page
  33. */
  34. define('ASSIGN_FILTER_SUBMITTED', 'submitted');
  35. define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
  36. define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
  37. /** Include accesslib.php */
  38. require_once($CFG->libdir.'/accesslib.php');
  39. /** Include formslib.php */
  40. require_once($CFG->libdir.'/formslib.php');
  41. /** Include repository/lib.php */
  42. require_once($CFG->dirroot . '/repository/lib.php');
  43. /** Include local mod_form.php */
  44. require_once($CFG->dirroot.'/mod/assign/mod_form.php');
  45. /** gradelib.php */
  46. require_once($CFG->libdir.'/gradelib.php');
  47. /** grading lib.php */
  48. require_once($CFG->dirroot.'/grade/grading/lib.php');
  49. /** Include feedbackplugin.php */
  50. require_once($CFG->dirroot.'/mod/assign/feedbackplugin.php');
  51. /** Include submissionplugin.php */
  52. require_once($CFG->dirroot.'/mod/assign/submissionplugin.php');
  53. /** Include renderable.php */
  54. require_once($CFG->dirroot.'/mod/assign/renderable.php');
  55. /** Include gradingtable.php */
  56. require_once($CFG->dirroot.'/mod/assign/gradingtable.php');
  57. /** Include eventslib.php */
  58. require_once($CFG->libdir.'/eventslib.php');
  59. /**
  60. * Standard base class for mod_assign (assignment types).
  61. *
  62. * @package mod_assign
  63. * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  64. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  65. */
  66. class assign {
  67. /** @var stdClass the assignment record that contains the global settings for this assign instance */
  68. private $instance;
  69. /** @var context the context of the course module for this assign instance (or just the course if we are
  70. creating a new one) */
  71. private $context;
  72. /** @var stdClass the course this assign instance belongs to */
  73. private $course;
  74. /** @var stdClass the admin config for all assign instances */
  75. private $adminconfig;
  76. /** @var assign_renderer the custom renderer for this module */
  77. private $output;
  78. /** @var stdClass the course module for this assign instance */
  79. private $coursemodule;
  80. /** @var array cache for things like the coursemodule name or the scale menu - only lives for a single
  81. request */
  82. private $cache;
  83. /** @var array list of the installed submission plugins */
  84. private $submissionplugins;
  85. /** @var array list of the installed feedback plugins */
  86. private $feedbackplugins;
  87. /** @var string action to be used to return to this page (without repeating any form submissions etc.) */
  88. private $returnaction = 'view';
  89. /** @var array params to be used to return to this page */
  90. private $returnparams = array();
  91. /** @var string modulename prevents excessive calls to get_string */
  92. private static $modulename = null;
  93. /** @var string modulenameplural prevents excessive calls to get_string */
  94. private static $modulenameplural = null;
  95. /**
  96. * Constructor for the base assign class
  97. *
  98. * @param mixed $coursemodulecontext context|null the course module context (or the course context if the coursemodule has not been created yet)
  99. * @param mixed $coursemodule the current course module if it was already loaded - otherwise this class will load one from the context as required
  100. * @param mixed $course the current course if it was already loaded - otherwise this class will load one from the context as required
  101. */
  102. public function __construct($coursemodulecontext, $coursemodule, $course) {
  103. global $PAGE;
  104. $this->context = $coursemodulecontext;
  105. $this->coursemodule = $coursemodule;
  106. $this->course = $course;
  107. $this->cache = array(); // temporary cache only lives for a single request - used to reduce db lookups
  108. $this->submissionplugins = $this->load_plugins('assignsubmission');
  109. $this->feedbackplugins = $this->load_plugins('assignfeedback');
  110. $this->output = $PAGE->get_renderer('mod_assign');
  111. }
  112. /**
  113. * Set the action and parameters that can be used to return to the current page
  114. *
  115. * @param string $action The action for the current page
  116. * @param array $params An array of name value pairs which form the parameters to return to the current page
  117. * @return void
  118. */
  119. public function register_return_link($action, $params) {
  120. $this->returnaction = $action;
  121. $this->returnparams = $params;
  122. }
  123. /**
  124. * Return an action that can be used to get back to the current page
  125. * @return string action
  126. */
  127. public function get_return_action() {
  128. return $this->returnaction;
  129. }
  130. /**
  131. * Based on the current assignment settings should we display the intro
  132. * @return bool showintro
  133. */
  134. private function show_intro() {
  135. if ($this->get_instance()->alwaysshowdescription ||
  136. time() > $this->get_instance()->allowsubmissionsfromdate) {
  137. return true;
  138. }
  139. return false;
  140. }
  141. /**
  142. * Return a list of parameters that can be used to get back to the current page
  143. * @return array params
  144. */
  145. public function get_return_params() {
  146. return $this->returnparams;
  147. }
  148. /**
  149. * Set the submitted form data
  150. * @param stdClass $data The form data (instance)
  151. */
  152. public function set_instance(stdClass $data) {
  153. $this->instance = $data;
  154. }
  155. /**
  156. * Set the context
  157. * @param context $context The new context
  158. */
  159. public function set_context(context $context) {
  160. $this->context = $context;
  161. }
  162. /**
  163. * Set the course data
  164. * @param stdClass $course The course data
  165. */
  166. public function set_course(stdClass $course) {
  167. $this->course = $course;
  168. }
  169. /**
  170. * get list of feedback plugins installed
  171. * @return array
  172. */
  173. public function get_feedback_plugins() {
  174. return $this->feedbackplugins;
  175. }
  176. /**
  177. * get list of submission plugins installed
  178. * @return array
  179. */
  180. public function get_submission_plugins() {
  181. return $this->submissionplugins;
  182. }
  183. /**
  184. * get a specific submission plugin by its type
  185. * @param string $subtype assignsubmission | assignfeedback
  186. * @param string $type
  187. * @return mixed assign_plugin|null
  188. */
  189. private function get_plugin_by_type($subtype, $type) {
  190. $shortsubtype = substr($subtype, strlen('assign'));
  191. $name = $shortsubtype . 'plugins';
  192. $pluginlist = $this->$name;
  193. foreach ($pluginlist as $plugin) {
  194. if ($plugin->get_type() == $type) {
  195. return $plugin;
  196. }
  197. }
  198. return null;
  199. }
  200. /**
  201. * Get a feedback plugin by type
  202. * @param string $type - The type of plugin e.g comments
  203. * @return mixed assign_feedback_plugin|null
  204. */
  205. public function get_feedback_plugin_by_type($type) {
  206. return $this->get_plugin_by_type('assignfeedback', $type);
  207. }
  208. /**
  209. * Get a submission plugin by type
  210. * @param string $type - The type of plugin e.g comments
  211. * @return mixed assign_submission_plugin|null
  212. */
  213. public function get_submission_plugin_by_type($type) {
  214. return $this->get_plugin_by_type('assignsubmission', $type);
  215. }
  216. /**
  217. * Load the plugins from the sub folders under subtype
  218. * @param string $subtype - either submission or feedback
  219. * @return array - The sorted list of plugins
  220. */
  221. private function load_plugins($subtype) {
  222. global $CFG;
  223. $result = array();
  224. $names = get_plugin_list($subtype);
  225. foreach ($names as $name => $path) {
  226. if (file_exists($path . '/locallib.php')) {
  227. require_once($path . '/locallib.php');
  228. $shortsubtype = substr($subtype, strlen('assign'));
  229. $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
  230. $plugin = new $pluginclass($this, $name);
  231. if ($plugin instanceof assign_plugin) {
  232. $idx = $plugin->get_sort_order();
  233. while (array_key_exists($idx, $result)) $idx +=1;
  234. $result[$idx] = $plugin;
  235. }
  236. }
  237. }
  238. ksort($result);
  239. return $result;
  240. }
  241. /**
  242. * Display the assignment, used by view.php
  243. *
  244. * The assignment is displayed differently depending on your role,
  245. * the settings for the assignment and the status of the assignment.
  246. * @param string $action The current action if any.
  247. * @return void
  248. */
  249. public function view($action='') {
  250. $o = '';
  251. $mform = null;
  252. // handle form submissions first
  253. if ($action == 'savesubmission') {
  254. $action = 'editsubmission';
  255. if ($this->process_save_submission($mform)) {
  256. $action = 'view';
  257. }
  258. } else if ($action == 'lock') {
  259. $this->process_lock();
  260. $action = 'grading';
  261. } else if ($action == 'reverttodraft') {
  262. $this->process_revert_to_draft();
  263. $action = 'grading';
  264. } else if ($action == 'unlock') {
  265. $this->process_unlock();
  266. $action = 'grading';
  267. } else if ($action == 'confirmsubmit') {
  268. $this->process_submit_for_grading();
  269. // save and show next button
  270. } else if ($action == 'batchgradingoperation') {
  271. $this->process_batch_grading_operation();
  272. $action = 'grading';
  273. } else if ($action == 'submitgrade') {
  274. if (optional_param('saveandshownext', null, PARAM_ALPHA)) {
  275. //save and show next
  276. $action = 'grade';
  277. if ($this->process_save_grade($mform)) {
  278. $action = 'nextgrade';
  279. }
  280. } else if (optional_param('nosaveandprevious', null, PARAM_ALPHA)) {
  281. $action = 'previousgrade';
  282. } else if (optional_param('nosaveandnext', null, PARAM_ALPHA)) {
  283. //show next button
  284. $action = 'nextgrade';
  285. } else if (optional_param('savegrade', null, PARAM_ALPHA)) {
  286. //save changes button
  287. $action = 'grade';
  288. if ($this->process_save_grade($mform)) {
  289. $action = 'grading';
  290. }
  291. } else {
  292. //cancel button
  293. $action = 'grading';
  294. }
  295. }else if ($action == 'quickgrade') {
  296. $message = $this->process_save_quick_grades();
  297. $action = 'quickgradingresult';
  298. }else if ($action == 'saveoptions') {
  299. $this->process_save_grading_options();
  300. $action = 'grading';
  301. }
  302. $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
  303. $this->register_return_link($action, $returnparams);
  304. // now show the right view page
  305. if ($action == 'previousgrade') {
  306. $mform = null;
  307. $o .= $this->view_single_grade_page($mform, -1);
  308. } else if ($action == 'quickgradingresult') {
  309. $mform = null;
  310. $o .= $this->view_quickgrading_result($message);
  311. } else if ($action == 'nextgrade') {
  312. $mform = null;
  313. $o .= $this->view_single_grade_page($mform, 1);
  314. } else if ($action == 'grade') {
  315. $o .= $this->view_single_grade_page($mform);
  316. } else if ($action == 'viewpluginassignfeedback') {
  317. $o .= $this->view_plugin_content('assignfeedback');
  318. } else if ($action == 'viewpluginassignsubmission') {
  319. $o .= $this->view_plugin_content('assignsubmission');
  320. } else if ($action == 'editsubmission') {
  321. $o .= $this->view_edit_submission_page($mform);
  322. } else if ($action == 'grading') {
  323. $o .= $this->view_grading_page();
  324. } else if ($action == 'downloadall') {
  325. $o .= $this->download_submissions();
  326. } else if ($action == 'submit') {
  327. $o .= $this->check_submit_for_grading();
  328. } else {
  329. $o .= $this->view_submission_page();
  330. }
  331. return $o;
  332. }
  333. /**
  334. * Add this instance to the database
  335. *
  336. * @param stdClass $formdata The data submitted from the form
  337. * @param bool $callplugins This is used to skip the plugin code
  338. * when upgrading an old assignment to a new one (the plugins get called manually)
  339. * @return mixed false if an error occurs or the int id of the new instance
  340. */
  341. public function add_instance(stdClass $formdata, $callplugins) {
  342. global $DB;
  343. $err = '';
  344. // add the database record
  345. $update = new stdClass();
  346. $update->name = $formdata->name;
  347. $update->timemodified = time();
  348. $update->timecreated = time();
  349. $update->course = $formdata->course;
  350. $update->courseid = $formdata->course;
  351. $update->intro = $formdata->intro;
  352. $update->introformat = $formdata->introformat;
  353. $update->alwaysshowdescription = $formdata->alwaysshowdescription;
  354. $update->preventlatesubmissions = $formdata->preventlatesubmissions;
  355. $update->submissiondrafts = $formdata->submissiondrafts;
  356. $update->sendnotifications = $formdata->sendnotifications;
  357. $update->sendlatenotifications = $formdata->sendlatenotifications;
  358. $update->duedate = $formdata->duedate;
  359. $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
  360. $update->grade = $formdata->grade;
  361. $returnid = $DB->insert_record('assign', $update);
  362. $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
  363. // cache the course record
  364. $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
  365. if ($callplugins) {
  366. // call save_settings hook for submission plugins
  367. foreach ($this->submissionplugins as $plugin) {
  368. if (!$this->update_plugin_instance($plugin, $formdata)) {
  369. print_error($plugin->get_error());
  370. return false;
  371. }
  372. }
  373. foreach ($this->feedbackplugins as $plugin) {
  374. if (!$this->update_plugin_instance($plugin, $formdata)) {
  375. print_error($plugin->get_error());
  376. return false;
  377. }
  378. }
  379. // in the case of upgrades the coursemodule has not been set so we need to wait before calling these two
  380. // TODO: add event to the calendar
  381. $this->update_calendar($formdata->coursemodule);
  382. // TODO: add the item in the gradebook
  383. $this->update_gradebook(false, $formdata->coursemodule);
  384. }
  385. $update = new stdClass();
  386. $update->id = $this->get_instance()->id;
  387. $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
  388. $DB->update_record('assign', $update);
  389. return $returnid;
  390. }
  391. /**
  392. * Delete all grades from the gradebook for this assignment
  393. *
  394. * @return bool
  395. */
  396. private function delete_grades() {
  397. global $CFG;
  398. return grade_update('mod/assign', $this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, 0, NULL, array('deleted'=>1)) == GRADE_UPDATE_OK;
  399. }
  400. /**
  401. * Delete this instance from the database
  402. *
  403. * @return bool false if an error occurs
  404. */
  405. public function delete_instance() {
  406. global $DB;
  407. $result = true;
  408. foreach ($this->submissionplugins as $plugin) {
  409. if (!$plugin->delete_instance()) {
  410. print_error($plugin->get_error());
  411. $result = false;
  412. }
  413. }
  414. foreach ($this->feedbackplugins as $plugin) {
  415. if (!$plugin->delete_instance()) {
  416. print_error($plugin->get_error());
  417. $result = false;
  418. }
  419. }
  420. // delete files associated with this assignment
  421. $fs = get_file_storage();
  422. if (! $fs->delete_area_files($this->context->id) ) {
  423. $result = false;
  424. }
  425. // delete_records will throw an exception if it fails - so no need for error checking here
  426. $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
  427. $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
  428. $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
  429. // delete items from the gradebook
  430. if (! $this->delete_grades()) {
  431. $result = false;
  432. }
  433. // delete the instance
  434. $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
  435. return $result;
  436. }
  437. /**
  438. * Update the settings for a single plugin
  439. *
  440. * @param assign_plugin $plugin The plugin to update
  441. * @param stdClass $formdata The form data
  442. * @return bool false if an error occurs
  443. */
  444. private function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
  445. if ($plugin->is_visible()) {
  446. $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
  447. if ($formdata->$enabledname) {
  448. $plugin->enable();
  449. if (!$plugin->save_settings($formdata)) {
  450. print_error($plugin->get_error());
  451. return false;
  452. }
  453. } else {
  454. $plugin->disable();
  455. }
  456. }
  457. return true;
  458. }
  459. /**
  460. * Update the gradebook information for this assignment
  461. *
  462. * @param bool $reset If true, will reset all grades in the gradbook for this assignment
  463. * @param int $coursemoduleid This is required because it might not exist in the database yet
  464. * @return bool
  465. */
  466. public function update_gradebook($reset, $coursemoduleid) {
  467. global $CFG;
  468. /** Include lib.php */
  469. require_once($CFG->dirroot.'/mod/assign/lib.php');
  470. $assign = clone $this->get_instance();
  471. $assign->cmidnumber = $coursemoduleid;
  472. $param = null;
  473. if ($reset) {
  474. $param = 'reset';
  475. }
  476. return assign_grade_item_update($assign, $param);
  477. }
  478. /** Load and cache the admin config for this module
  479. *
  480. * @return stdClass the plugin config
  481. */
  482. public function get_admin_config() {
  483. if ($this->adminconfig) {
  484. return $this->adminconfig;
  485. }
  486. $this->adminconfig = get_config('assign');
  487. return $this->adminconfig;
  488. }
  489. /**
  490. * Update the calendar entries for this assignment
  491. *
  492. * @param int $coursemoduleid - Required to pass this in because it might not exist in the database yet
  493. * @return bool
  494. */
  495. public function update_calendar($coursemoduleid) {
  496. global $DB, $CFG;
  497. require_once($CFG->dirroot.'/calendar/lib.php');
  498. // special case for add_instance as the coursemodule has not been set yet.
  499. if ($this->get_instance()->duedate) {
  500. $event = new stdClass();
  501. if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id))) {
  502. $event->name = $this->get_instance()->name;
  503. $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
  504. $event->timestart = $this->get_instance()->duedate;
  505. $calendarevent = calendar_event::load($event->id);
  506. $calendarevent->update($event);
  507. } else {
  508. $event = new stdClass();
  509. $event->name = $this->get_instance()->name;
  510. $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
  511. $event->courseid = $this->get_instance()->course;
  512. $event->groupid = 0;
  513. $event->userid = 0;
  514. $event->modulename = 'assign';
  515. $event->instance = $this->get_instance()->id;
  516. $event->eventtype = 'due';
  517. $event->timestart = $this->get_instance()->duedate;
  518. $event->timeduration = 0;
  519. calendar_event::create($event);
  520. }
  521. } else {
  522. $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id));
  523. }
  524. }
  525. /**
  526. * Update this instance in the database
  527. *
  528. * @param stdClass $formdata - the data submitted from the form
  529. * @return bool false if an error occurs
  530. */
  531. public function update_instance($formdata) {
  532. global $DB;
  533. $update = new stdClass();
  534. $update->id = $formdata->instance;
  535. $update->name = $formdata->name;
  536. $update->timemodified = time();
  537. $update->course = $formdata->course;
  538. $update->intro = $formdata->intro;
  539. $update->introformat = $formdata->introformat;
  540. $update->alwaysshowdescription = $formdata->alwaysshowdescription;
  541. $update->preventlatesubmissions = $formdata->preventlatesubmissions;
  542. $update->submissiondrafts = $formdata->submissiondrafts;
  543. $update->sendnotifications = $formdata->sendnotifications;
  544. $update->sendlatenotifications = $formdata->sendlatenotifications;
  545. $update->duedate = $formdata->duedate;
  546. $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
  547. $update->grade = $formdata->grade;
  548. $result = $DB->update_record('assign', $update);
  549. $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
  550. // load the assignment so the plugins have access to it
  551. // call save_settings hook for submission plugins
  552. foreach ($this->submissionplugins as $plugin) {
  553. if (!$this->update_plugin_instance($plugin, $formdata)) {
  554. print_error($plugin->get_error());
  555. return false;
  556. }
  557. }
  558. foreach ($this->feedbackplugins as $plugin) {
  559. if (!$this->update_plugin_instance($plugin, $formdata)) {
  560. print_error($plugin->get_error());
  561. return false;
  562. }
  563. }
  564. // update the database record
  565. // update all the calendar events
  566. $this->update_calendar($this->get_course_module()->id);
  567. $this->update_gradebook(false, $this->get_course_module()->id);
  568. $update = new stdClass();
  569. $update->id = $this->get_instance()->id;
  570. $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
  571. $DB->update_record('assign', $update);
  572. return $result;
  573. }
  574. /**
  575. * add elements in grading plugin form
  576. *
  577. * @param mixed $grade stdClass|null
  578. * @param MoodleQuickForm $mform
  579. * @param stdClass $data
  580. * @return void
  581. */
  582. private function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data) {
  583. foreach ($this->feedbackplugins as $plugin) {
  584. if ($plugin->is_enabled() && $plugin->is_visible()) {
  585. $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
  586. if (!$plugin->get_form_elements($grade, $mform, $data)) {
  587. $mform->removeElement('header_' . $plugin->get_type());
  588. }
  589. }
  590. }
  591. }
  592. /**
  593. * Add one plugins settings to edit plugin form
  594. *
  595. * @param assign_plugin $plugin The plugin to add the settings from
  596. * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
  597. * @return void
  598. */
  599. private function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform) {
  600. global $CFG;
  601. if ($plugin->is_visible()) {
  602. // enabled
  603. //tied disableIf rule to this select element
  604. $mform->addElement('selectyesno', $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $plugin->get_name());
  605. $mform->addHelpButton($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', 'enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
  606. $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
  607. if ($plugin->get_config('enabled') !== false) {
  608. $default = $plugin->is_enabled();
  609. }
  610. $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
  611. $plugin->get_settings($mform);
  612. }
  613. }
  614. /**
  615. * Add settings to edit plugin form
  616. *
  617. * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
  618. * @return void
  619. */
  620. public function add_all_plugin_settings(MoodleQuickForm $mform) {
  621. $mform->addElement('header', 'general', get_string('submissionsettings', 'assign'));
  622. foreach ($this->submissionplugins as $plugin) {
  623. $this->add_plugin_settings($plugin, $mform);
  624. }
  625. $mform->addElement('header', 'general', get_string('feedbacksettings', 'assign'));
  626. foreach ($this->feedbackplugins as $plugin) {
  627. $this->add_plugin_settings($plugin, $mform);
  628. }
  629. }
  630. /**
  631. * Allow each plugin an opportunity to update the defaultvalues
  632. * passed in to the settings form (needed to set up draft areas for
  633. * editor and filemanager elements)
  634. * @param array $defaultvalues
  635. */
  636. public function plugin_data_preprocessing(&$defaultvalues) {
  637. foreach ($this->submissionplugins as $plugin) {
  638. if ($plugin->is_visible()) {
  639. $plugin->data_preprocessing($defaultvalues);
  640. }
  641. }
  642. foreach ($this->feedbackplugins as $plugin) {
  643. if ($plugin->is_visible()) {
  644. $plugin->data_preprocessing($defaultvalues);
  645. }
  646. }
  647. }
  648. /**
  649. * Get the name of the current module.
  650. *
  651. * @return string the module name (Assignment)
  652. */
  653. protected function get_module_name() {
  654. if (isset(self::$modulename)) {
  655. return self::$modulename;
  656. }
  657. self::$modulename = get_string('modulename', 'assign');
  658. return self::$modulename;
  659. }
  660. /**
  661. * Get the plural name of the current module.
  662. *
  663. * @return string the module name plural (Assignments)
  664. */
  665. protected function get_module_name_plural() {
  666. if (isset(self::$modulenameplural)) {
  667. return self::$modulenameplural;
  668. }
  669. self::$modulenameplural = get_string('modulenameplural', 'assign');
  670. return self::$modulenameplural;
  671. }
  672. /**
  673. * Has this assignment been constructed from an instance?
  674. *
  675. * @return bool
  676. */
  677. public function has_instance() {
  678. return $this->instance || $this->get_course_module();
  679. }
  680. /**
  681. * Get the settings for the current instance of this assignment
  682. *
  683. * @return stdClass The settings
  684. */
  685. public function get_instance() {
  686. global $DB;
  687. if ($this->instance) {
  688. return $this->instance;
  689. }
  690. if ($this->get_course_module()) {
  691. $this->instance = $DB->get_record('assign', array('id' => $this->get_course_module()->instance), '*', MUST_EXIST);
  692. }
  693. if (!$this->instance) {
  694. throw new coding_exception('Improper use of the assignment class. Cannot load the assignment record.');
  695. }
  696. return $this->instance;
  697. }
  698. /**
  699. * Get the context of the current course
  700. * @return mixed context|null The course context
  701. */
  702. public function get_course_context() {
  703. if (!$this->context && !$this->course) {
  704. throw new coding_exception('Improper use of the assignment class. Cannot load the course context.');
  705. }
  706. if ($this->context) {
  707. return $this->context->get_course_context();
  708. } else {
  709. return context_course::instance($this->course->id);
  710. }
  711. }
  712. /**
  713. * Get the current course module
  714. *
  715. * @return mixed stdClass|null The course module
  716. */
  717. public function get_course_module() {
  718. if ($this->coursemodule) {
  719. return $this->coursemodule;
  720. }
  721. if (!$this->context) {
  722. return null;
  723. }
  724. if ($this->context->contextlevel == CONTEXT_MODULE) {
  725. $this->coursemodule = get_coursemodule_from_id('assign', $this->context->instanceid, 0, false, MUST_EXIST);
  726. return $this->coursemodule;
  727. }
  728. return null;
  729. }
  730. /**
  731. * Get context module
  732. *
  733. * @return context
  734. */
  735. public function get_context() {
  736. return $this->context;
  737. }
  738. /**
  739. * Get the current course
  740. * @return mixed stdClass|null The course
  741. */
  742. public function get_course() {
  743. global $DB;
  744. if ($this->course) {
  745. return $this->course;
  746. }
  747. if (!$this->context) {
  748. return null;
  749. }
  750. $this->course = $DB->get_record('course', array('id' => $this->get_course_context()->instanceid), '*', MUST_EXIST);
  751. return $this->course;
  752. }
  753. /**
  754. * Return a grade in user-friendly form, whether it's a scale or not
  755. *
  756. * @param mixed $grade int|null
  757. * @param boolean $editing Are we allowing changes to this grade?
  758. * @param int $userid The user id the grade belongs to
  759. * @param int $modified Timestamp from when the grade was last modified
  760. * @return string User-friendly representation of grade
  761. */
  762. public function display_grade($grade, $editing, $userid=0, $modified=0) {
  763. global $DB;
  764. static $scalegrades = array();
  765. if ($this->get_instance()->grade >= 0) {
  766. // Normal number
  767. if ($editing && $this->get_instance()->grade > 0) {
  768. if ($grade < 0) {
  769. $displaygrade = '';
  770. } else {
  771. $displaygrade = format_float($grade);
  772. }
  773. $o = '<input type="text" name="quickgrade_' . $userid . '" value="' . $displaygrade . '" size="6" maxlength="10" class="quickgrade"/>';
  774. $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade,2);
  775. $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
  776. return $o;
  777. } else {
  778. if ($grade == -1 || $grade === null) {
  779. return '-';
  780. } else {
  781. return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
  782. }
  783. }
  784. } else {
  785. // Scale
  786. if (empty($this->cache['scale'])) {
  787. if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
  788. $this->cache['scale'] = make_menu_from_list($scale->scale);
  789. } else {
  790. return '-';
  791. }
  792. }
  793. if ($editing) {
  794. $o = '<select name="quickgrade_' . $userid . '" class="quickgrade">';
  795. $o .= '<option value="-1">' . get_string('nograde') . '</option>';
  796. foreach ($this->cache['scale'] as $optionid => $option) {
  797. $selected = '';
  798. if ($grade == $optionid) {
  799. $selected = 'selected="selected"';
  800. }
  801. $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
  802. }
  803. $o .= '</select>';
  804. $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
  805. return $o;
  806. } else {
  807. $scaleid = (int)$grade;
  808. if (isset($this->cache['scale'][$scaleid])) {
  809. return $this->cache['scale'][$scaleid];
  810. }
  811. return '-';
  812. }
  813. }
  814. }
  815. /**
  816. * Load a list of users enrolled in the current course with the specified permission and group (0 for no group)
  817. *
  818. * @param int $currentgroup
  819. * @param bool $idsonly
  820. * @return array List of user records
  821. */
  822. public function list_participants($currentgroup, $idsonly) {
  823. if ($idsonly) {
  824. return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup, 'u.id');
  825. } else {
  826. return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
  827. }
  828. }
  829. /**
  830. * Load a count of users enrolled in the current course with the specified permission and group (0 for no group)
  831. *
  832. * @param int $currentgroup
  833. * @return int number of matching users
  834. */
  835. public function count_participants($currentgroup) {
  836. return count_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
  837. }
  838. /**
  839. * Load a count of users enrolled in the current course with the specified permission and group (optional)
  840. *
  841. * @param string $status The submission status - should match one of the constants
  842. * @return int number of matching submissions
  843. */
  844. public function count_submissions_with_status($status) {
  845. global $DB;
  846. return $DB->count_records_sql("SELECT COUNT('x')
  847. FROM {assign_submission}
  848. WHERE assignment = ? AND
  849. status = ?", array($this->get_course_module()->instance, $status));
  850. }
  851. /**
  852. * Utility function to get the userid for every row in the grading table
  853. * so the order can be frozen while we iterate it
  854. *
  855. * @return array An array of userids
  856. */
  857. private function get_grading_userid_list(){
  858. $filter = get_user_preferences('assign_filter', '');
  859. $table = new assign_grading_table($this, 0, $filter, 0, false);
  860. $useridlist = $table->get_column_data('userid');
  861. return $useridlist;
  862. }
  863. /**
  864. * Utility function get the userid based on the row number of the grading table.
  865. * This takes into account any active filters on the table.
  866. *
  867. * @param int $num The row number of the user
  868. * @param bool $last This is set to true if this is the last user in the table
  869. * @return mixed The user id of the matching user or false if there was an error
  870. */
  871. private function get_userid_for_row($num, $last){
  872. if (!array_key_exists('userid_for_row', $this->cache)) {
  873. $this->cache['userid_for_row'] = array();
  874. }
  875. if (array_key_exists($num, $this->cache['userid_for_row'])) {
  876. list($userid, $last) = $this->cache['userid_for_row'][$num];
  877. return $userid;
  878. }
  879. $filter = get_user_preferences('assign_filter', '');
  880. $table = new assign_grading_table($this, 0, $filter, 0, false);
  881. $userid = $table->get_cell_data($num, 'userid', $last);
  882. $this->cache['userid_for_row'][$num] = array($userid, $last);
  883. return $userid;
  884. }
  885. /**
  886. * Return all assignment submissions by ENROLLED students (even empty)
  887. *
  888. * @param string $sort optional field names for the ORDER BY in the sql query
  889. * @param string $dir optional specifying the sort direction, defaults to DESC
  890. * @return array The submission objects indexed by id
  891. */
  892. private function get_all_submissions( $sort="", $dir="DESC") {
  893. global $CFG, $DB;
  894. if ($sort == "lastname" or $sort == "firstname") {
  895. $sort = "u.$sort $dir";
  896. } else if (empty($sort)) {
  897. $sort = "a.timemodified DESC";
  898. } else {
  899. $sort = "a.$sort $dir";
  900. }
  901. return $DB->get_records_sql("SELECT a.*
  902. FROM {assign_submission} a, {user} u
  903. WHERE u.id = a.userid
  904. AND a.assignment = ?
  905. ORDER BY $sort", array($this->get_instance()->id));
  906. }
  907. /**
  908. * Generate zip file from array of given files
  909. *
  910. * @param array $filesforzipping - array of files to pass into archive_to_pathname - this array is indexed by the final file name and each element in the array is an instance of a stored_file object
  911. * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
  912. */
  913. private function pack_files($filesforzipping) {
  914. global $CFG;
  915. //create path for new zip file.
  916. $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
  917. //zip files
  918. $zipper = new zip_packer();
  919. if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
  920. return $tempzip;
  921. }
  922. return false;
  923. }
  924. /**
  925. * Finds all assignment notifications that have yet to be mailed out, and mails them.
  926. *
  927. * Cron function to be run periodically according to the moodle cron
  928. *
  929. * @return bool
  930. */
  931. static function cron() {
  932. global $DB;
  933. // only ever send a max of one days worth of updates
  934. $yesterday = time() - (24 * 3600);
  935. $timenow = time();
  936. // Collect all submissions from the past 24 hours that require mailing.
  937. $sql = "SELECT s.*, a.course, a.name, g.*, g.id as gradeid, g.timemodified as lastmodified
  938. FROM {assign} a
  939. JOIN {assign_grades} g ON g.assignment = a.id
  940. LEFT JOIN {assign_submission} s ON s.assignment = a.id AND s.userid = g.userid
  941. WHERE g.timemodified >= :yesterday AND
  942. g.timemodified <= :today AND
  943. g.mailed = 0";
  944. $params = array('yesterday' => $yesterday, 'today' => $timenow);
  945. $submissions = $DB->get_records_sql($sql, $params);
  946. if (empty($submissions)) {
  947. mtrace('done.');
  948. return true;
  949. }
  950. mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
  951. // Preload courses we are going to need those.
  952. $courseids = array();
  953. foreach ($submissions as $submission) {
  954. $courseids[] = $submission->course;
  955. }
  956. // Filter out duplicates
  957. $courseids = array_unique($courseids);
  958. $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
  959. list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
  960. $sql = "SELECT c.*, {$ctxselect}
  961. FROM {course} c
  962. LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
  963. WHERE c.id {$courseidsql}";
  964. $params['contextlevel'] = CONTEXT_COURSE;
  965. $courses = $DB->get_records_sql($sql, $params);
  966. // Clean up... this could go on for a while.
  967. unset($courseids);
  968. unset($ctxselect);
  969. unset($courseidsql);
  970. unset($params);
  971. // Simple array we'll use for caching modules.
  972. $modcache = array();
  973. // Message students about new feedback
  974. foreach ($submissions as $submission) {
  975. mtrace("Processing assignment submission $submission->id ...");
  976. // do not cache user lookups - could be too many
  977. if (!$user = $DB->get_record("user", array("id"=>$submission->userid))) {
  978. mtrace("Could not find user $submission->userid");
  979. continue;
  980. }
  981. // use a cache to prevent the same DB queries happening over and over
  982. if (!array_key_exists($submission->course, $courses)) {
  983. mtrace("Could not find course $submission->course");
  984. continue;
  985. }
  986. $course = $courses[$submission->course];
  987. if (isset($course->ctxid)) {
  988. // Context has not yet been preloaded. Do so now.
  989. context_helper::preload_from_record($course);
  990. }
  991. // Override the language and timezone of the "current" user, so that
  992. // mail is customised for the receiver.
  993. cron_setup_user($user, $course);
  994. // context lookups are already cached
  995. $coursecontext = context_course::instance($course->id);
  996. if (!is_enrolled($coursecontext, $user->id)) {
  997. $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
  998. mtrace(fullname($user)." not an active participant in " . $courseshortname);
  999. continue;
  1000. }
  1001. if (!$grader = $DB->get_record("user", array("id"=>$submission->grader))) {
  1002. mtrace("Could not find grader $submission->grader");
  1003. continue;
  1004. }
  1005. if (!array_key_exists($submission->assignment, $modcache)) {
  1006. if (! $mod = get_coursemodule_from_instance("assign", $submission->assignment, $course->id)) {
  1007. mtrace("Could not find course module for assignment id $submission->assignment");
  1008. continue;
  1009. }
  1010. $modcache[$submission->assignment] = $mod;
  1011. } else {
  1012. $mod = $modcache[$submission->assignment];
  1013. }
  1014. // context lookups are already cached
  1015. $contextmodule = context_module::instance($mod->id);
  1016. if (!$mod->visible) {
  1017. // Hold mail notification for hidden assignments until later
  1018. continue;
  1019. }
  1020. // need to send this to the student
  1021. $messagetype = 'feedbackavailable';
  1022. $eventtype = 'assign_notification';
  1023. $updatetime = $submission->lastmodified;
  1024. $modulename = get_string('modulename', 'assign');
  1025. self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime, $mod, $contextmodule, $course, $modulename, $submission->name);
  1026. $grade = new stdClass();
  1027. $grade->id = $submission->gradeid;
  1028. $grade->mailed = 1;
  1029. $DB->update_record('assign_grades', $grade);
  1030. mtrace('Done');
  1031. }
  1032. mtrace('Done processing ' . count($submissions) . ' assignment submissions');
  1033. cron_setup_user();
  1034. // Free up memory just to be sure
  1035. unset($courses);
  1036. unset($modcache);
  1037. return true;
  1038. }
  1039. /**
  1040. * Update a grade in the grade table for the assignment and in the gradebook
  1041. *
  1042. * @param stdClass $grade a grade record keyed on id
  1043. * @return bool true for success
  1044. */
  1045. private function update_grade($grade) {
  1046. global $DB;
  1047. $grade->timemodified = time();
  1048. if ($grade->grade && $grade->grade != -1) {
  1049. if ($this->get_instance()->grade > 0) {
  1050. if (!is_numeric($grade->grade)) {
  1051. return false;
  1052. } else if ($grade->grade > $this->get_instance()->grade) {
  1053. return false;
  1054. } else if ($grade->grade < 0) {
  1055. return false;
  1056. }
  1057. } else {
  1058. // this is a scale
  1059. if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
  1060. $scaleoptions = make_menu_from_list($scale->scale);
  1061. if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
  1062. return false;
  1063. }
  1064. }
  1065. }
  1066. }
  1067. $result = $DB->update_record('assign_grades', $grade);
  1068. if ($result) {
  1069. $this->gradebook_item_update(null, $grade);
  1070. }
  1071. return $result;
  1072. }
  1073. /**
  1074. * display the submission that is used by a plugin
  1075. * Uses url parameters 'sid', 'gid' and 'plugin'
  1076. * @param string $pluginsubtype
  1077. * @return string
  1078. */
  1079. private function view_plugin_content($pluginsubtype) {
  1080. global $USER;
  1081. $o = '';
  1082. $submissionid = optional_param('sid', 0, PARAM_INT);
  1083. $gradeid = optional_param('gid', 0, PARAM_INT);
  1084. $plugintype = required_param('plugin', PARAM_TEXT);
  1085. $item = null;
  1086. if ($pluginsubtype == 'assignsubmission') {
  1087. $plugin = $this->get_submission_plugin_by_type($plugintype);
  1088. if ($submissionid <= 0) {
  1089. throw new coding_exception('Submission id should not be 0');
  1090. }
  1091. $item = $this->get_submission($submissionid);
  1092. // permissions
  1093. if ($item->userid != $USER->id) {
  1094. require_capability('mod/assign:grade', $this->context);
  1095. }
  1096. $o .= $this->output->render(new assign_header($this->get_instance(),
  1097. $this->get_context(),
  1098. $this->show_intro(),
  1099. $this->get_course_module()->id,
  1100. $plugin->get_name()));
  1101. $o .= $this->output->render(new assign_submission_plugin_submission($plugin,
  1102. $item,
  1103. assign_submission_plugin_submission::FULL,
  1104. $this->get_course_module()->id,
  1105. $this->get_return_action(),
  1106. $this->get_return_params()));
  1107. $this->add_to_log('view submission', get_string('viewsubmissionforuser', 'assign', $item->userid));
  1108. } else {
  1109. $plugin = $this->get_feedback_plugin_by_type($plugintype);
  1110. if ($gradeid <= 0) {
  1111. throw new coding_exception('Grade id should not be 0');
  1112. }
  1113. $item = $this->get_grade($gradeid);
  1114. // permissions
  1115. if ($item->userid != $USER->id) {
  1116. require_capability('mod/assign:grade', $this->context);
  1117. }
  1118. $o .= $this->output->render(new assign_header($this->get_instance(),
  1119. $this->get_context(),
  1120. $this->show_intro(),
  1121. $this->get_course_module()->id,
  1122. $plugin->get_name()));
  1123. $o .= $this->output->render(new assign_feedback_plugin_feedback($plugin,
  1124. $item,
  1125. assign_feedback_plugin_feedback::FULL,
  1126. $this->get_course_module()->id,
  1127. $this->get_return_action(),
  1128. $this->get_return_params()));
  1129. $this->add_to_log('view feedback', get_string('viewfeedbackforuser', 'assign', $item->userid));
  1130. }
  1131. $o .= $this->view_return_links();
  1132. $o .= $this->view_footer();
  1133. return $o;
  1134. }
  1135. /**
  1136. * render the content in editor that is often used by plugin
  1137. *
  1138. * @param string $filearea
  1139. * @param int $submissionid
  1140. * @param string $plugintype
  1141. * @param string $editor
  1142. * @param string $component
  1143. * @return string
  1144. */
  1145. public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
  1146. global $CFG;
  1147. $result = '';
  1148. $plugin = $this->get_submission_plugin_by_type($plugintype);
  1149. $text = $plugin->get_editor_text($editor, $submissionid);
  1150. $format = $plugin->get_editor_format($editor, $submissionid);
  1151. $finaltext = file_rewrite_pluginfile_urls($text, 'pluginfile.php', $this->get_context()->id, $component, $filearea, $submissionid);
  1152. $result .= format_text($finaltext, $format, array('overflowdiv' => true, 'context' => $this->get_context()));
  1153. if ($CFG->enableportfolios) {
  1154. require_once($CFG->libdir . '/portfoliolib.php');
  1155. $button = new portfolio_add_button();
  1156. $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->get_course_module()->id, 'sid' => $submissionid, 'plugin' => $plugintype, 'editor' => $editor, 'area'=>$filearea), '/mod/assign/portfolio_callback.php');
  1157. $fs = get_file_storage();
  1158. if ($files = $fs->get_area_files($this->context->id, $component,$filearea, $submissionid, "timemodified", false)) {
  1159. $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
  1160. } else {
  1161. $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
  1162. }
  1163. $result .= $button->to_html();
  1164. }
  1165. return $result;
  1166. }
  1167. /**
  1168. * Display a grading error
  1169. *
  1170. * @param string $message - The description of the result
  1171. * @return string
  1172. */
  1173. private function view_quickgrading_result($message) {
  1174. $o = '';
  1175. $o .= $this->output->render(new assign_header($this->get_instance(),
  1176. $this->get_context(),
  1177. $this->show_intro(),
  1178. $this->get_course_module()->id,
  1179. get_string('quickgradingresult', 'assign')));
  1180. $o .= $this->output->render(new assign_quickgrading_result($message, $this->get_course_module()->id));
  1181. $o .= $this->view_footer();
  1182. return $o;
  1183. }
  1184. /**
  1185. * Display the page footer
  1186. *
  1187. * @return string
  1188. */
  1189. private function view_footer() {
  1190. return $this->output->render_footer();
  1191. }
  1192. /**
  1193. * Does this user have grade permission for this assignment
  1194. *
  1195. * @return bool
  1196. */
  1197. private function can_grade() {
  1198. // Permissions check
  1199. if (!has_capability('mod/assign:grade', $this->context)) {
  1200. return false;
  1201. }
  1202. return true;
  1203. }
  1204. /**
  1205. * Download a zip file of all assignment submissions
  1206. *
  1207. * @return void
  1208. */
  1209. private function download_submissions() {
  1210. global $CFG,$DB;
  1211. // more efficient to load this here
  1212. require_once($CFG->libdir.'/filelib.php');
  1213. // load all submissions
  1214. $submissions = $this->get_all_submissions('','');
  1215. if (empty($submissions)) {
  1216. print_error('errornosubmissions', 'assign');
  1217. return;
  1218. }
  1219. // build a list of files to zip
  1220. $filesforzipping = array();
  1221. $fs = get_file_storage();
  1222. $groupmode = groups_get_activity_groupmode($this->get_course_module());
  1223. $groupid = 0; // All users
  1224. $groupname = '';
  1225. if ($groupmode) {
  1226. $groupid = groups_get_activity_group($this->get_course_module(), true);
  1227. $groupname = groups_get_group_name($groupid).'-';
  1228. }
  1229. // construct the zip file name
  1230. $filename = str_replace(' ', '_', clean_filename($this->get_course()->shortname.'-'.$this->get_instance()->name.'-'.$groupname.$this->get_course_module()->id.".zip")); //name of new zip file.
  1231. // get all the files for each submission
  1232. foreach ($submissions as $submission) {
  1233. $userid = $submission->userid; //get userid
  1234. if ((groups_is_member($groupid,$userid) or !$groupmode or !$groupid)) {
  1235. // get the plugins to add their own files to the zip
  1236. $user = $DB->get_record("user", array("id"=>$userid),'id,username,firstname,lastname', MUST_EXIST);
  1237. $prefix = clean_filename(fullname($user) . "_" .$userid . "_");
  1238. foreach ($this->submissionplugins as $plugin) {
  1239. if ($plugin->is_enabled() && $plugin->is_visible()) {
  1240. $pluginfiles = $plugin->get_files($submission);
  1241. foreach ($pluginfiles as $zipfilename => $file) {
  1242. $filesforzipping[$prefix . $zipfilename] = $file;
  1243. }
  1244. }
  1245. }
  1246. }
  1247. } // end of foreach loop
  1248. if ($zipfile = $this->pack_files($filesforzipping)) {
  1249. $this->add_to_log('download all submissions', get_string('downloadall', 'assign'));
  1250. send_temp_file($zipfile, $filename); //send file and delete after sending.
  1251. }
  1252. }
  1253. /**
  1254. * Util function to add a message to the log
  1255. *
  1256. * @param string $action The current action
  1257. * @param string $info A detailed description of the change. But no more than 255 characters.
  1258. * @param string $url The url to the assign module instance.
  1259. * @return void
  1260. */
  1261. public function add_to_log($action = '', $info = '', $url='') {
  1262. global $USER;
  1263. $fullurl = 'view.php?id=' . $this->get_course_module()->id;
  1264. if ($url != '') {
  1265. $fullurl .= '&' . $url;
  1266. }
  1267. add_to_log($this->get_course()->id, 'assign', $action, $fullurl, $info, $this->get_course_module()->id, $USER->id);
  1268. }
  1269. /**
  1270. * Load the submission object for a particular user, optionally creating it if required
  1271. *
  1272. * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
  1273. * @param bool $create optional Defaults to false. If set to true a new submission object will be created in the database
  1274. * @return stdClass The submission
  1275. */
  1276. private function get_user_submission($userid, $create) {
  1277. global $DB, $USER;
  1278. if (!$userid) {
  1279. $userid = $USER->id;
  1280. }
  1281. // if the userid is not null then use userid
  1282. $submission = $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
  1283. if ($submission) {
  1284. return $submission;
  1285. }
  1286. if ($create) {
  1287. $submission = new stdClass();
  1288. $submission->assignment = $this->get_instance()->id;
  1289. $submission->userid = $userid;
  1290. $submission->timecreated = time();
  1291. $submission->timemodified = $submission->timecreated;
  1292. if ($this->get_instance()->submissiondrafts) {
  1293. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  1294. } else {
  1295. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  1296. }
  1297. $sid = $DB->insert_record('assign_submission', $submission);
  1298. $submission->id = $sid;
  1299. return $submission;
  1300. }
  1301. return false;
  1302. }
  1303. /**
  1304. * Load the submission object from it's id
  1305. *
  1306. * @param int $submissionid The id of the submission we want
  1307. * @return stdClass The submission
  1308. */
  1309. private function get_submission($submissionid) {
  1310. global $DB;
  1311. return $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid), '*', MUST_EXIST);
  1312. }
  1313. /**
  1314. * This will retrieve a grade object from the db, optionally creating it if required
  1315. *
  1316. * @param int $userid The user we are grading
  1317. * @param bool $create If true the grade will be created if it does not exist
  1318. * @return stdClass The grade record
  1319. */
  1320. private function get_user_grade($userid, $create) {
  1321. global $DB, $USER;
  1322. if (!$userid) {
  1323. $userid = $USER->id;
  1324. }
  1325. // if the userid is not null then use userid
  1326. $grade = $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
  1327. if ($grade) {
  1328. return $grade;
  1329. }
  1330. if ($create) {
  1331. $grade = new stdClass();
  1332. $grade->assignment = $this->get_instance()->id;
  1333. $grade->userid = $userid;
  1334. $grade->timecreated = time();
  1335. $grade->timemodified = $grade->timecreated;
  1336. $grade->locked = 0;
  1337. $grade->grade = -1;
  1338. $grade->grader = $USER->id;
  1339. $gid = $DB->insert_record('assign_grades', $grade);
  1340. $grade->id = $gid;
  1341. return $grade;
  1342. }
  1343. return false;
  1344. }
  1345. /**
  1346. * This will retrieve a grade object from the db
  1347. *
  1348. * @param int $gradeid The id of the grade
  1349. * @return stdClass The grade record
  1350. */
  1351. private function get_grade($gradeid) {
  1352. global $DB;
  1353. return $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid), '*', MUST_EXIST);
  1354. }
  1355. /**
  1356. * Print the grading page for a single user submission
  1357. *
  1358. * @param moodleform $mform
  1359. * @param int $offset
  1360. * @return string
  1361. */
  1362. private function view_single_grade_page($mform, $offset=0) {
  1363. global $DB, $CFG;
  1364. $o = '';
  1365. // Include grade form
  1366. require_once($CFG->dirroot . '/mod/assign/gradeform.php');
  1367. // Need submit permission to submit an assignment
  1368. require_capability('mod/assign:grade', $this->context);
  1369. $o .= $this->output->render(new assign_header($this->get_instance(),
  1370. $this->get_context(), false, $this->get_course_module()->id,get_string('grading', 'assign')));
  1371. $rownum = required_param('rownum', PARAM_INT) + $offset;
  1372. $useridlist = optional_param('useridlist', '', PARAM_TEXT);
  1373. if ($useridlist) {
  1374. $useridlist = explode(',', $useridlist);
  1375. } else {
  1376. $useridlist = $this->get_grading_userid_list();
  1377. }
  1378. $last = false;
  1379. $userid = $useridlist[$rownum];
  1380. if ($rownum == count($useridlist) - 1) {
  1381. $last = true;
  1382. }
  1383. // the placement of this is important so can pass the list of userids above
  1384. if ($offset) {
  1385. $_POST = array();
  1386. }
  1387. if(!$userid){
  1388. throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
  1389. }
  1390. $user = $DB->get_record('user', array('id' => $userid));
  1391. if ($user) {
  1392. $o .= $this->output->render(new assign_user_summary($user, $this->get_course()->id, has_capability('moodle/site:viewfullnames', $this->get_course_context())));
  1393. }
  1394. $submission = $this->get_user_submission($userid, false);
  1395. // get the current grade
  1396. $grade = $this->get_user_grade($userid, false);
  1397. if ($this->can_view_submission($userid)) {
  1398. $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($userid);
  1399. $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
  1400. $this->get_instance()->alwaysshowdescription,
  1401. $submission,
  1402. $this->is_any_submission_plugin_enabled(),
  1403. $gradelocked,
  1404. $this->is_graded($userid),
  1405. $this->get_instance()->duedate,
  1406. $this->get_submission_plugins(),
  1407. $this->get_return_action(),
  1408. $this->get_return_params(),
  1409. $this->get_course_module()->id,
  1410. assign_submission_status::GRADER_VIEW,
  1411. false,
  1412. false));
  1413. }
  1414. if ($grade) {
  1415. $data = new stdClass();
  1416. if ($grade->grade !== NULL && $grade->grade >= 0) {
  1417. $data->grade = format_float($grade->grade,2);
  1418. }
  1419. } else {
  1420. $data = new stdClass();
  1421. $data->grade = '';
  1422. }
  1423. // now show the grading form
  1424. if (!$mform) {
  1425. $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last)), 'post', '', array('class'=>'gradeform'));
  1426. }
  1427. $o .= $this->output->render(new assign_form('gradingform',$mform));
  1428. $this->add_to_log('view grading form', get_string('viewgradingformforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
  1429. $o .= $this->view_footer();
  1430. return $o;
  1431. }
  1432. /**
  1433. * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
  1434. *
  1435. * @return string
  1436. */
  1437. private function view_return_links() {
  1438. $returnaction = optional_param('returnaction','', PARAM_ALPHA);
  1439. $returnparams = optional_param('returnparams','', PARAM_TEXT);
  1440. $params = array();
  1441. parse_str($returnparams, $params);
  1442. $params = array_merge( array('id' => $this->get_course_module()->id, 'action' => $returnaction), $params);
  1443. return $this->output->single_button(new moodle_url('/mod/assign/view.php', $params), get_string('back'), 'get');
  1444. }
  1445. /**
  1446. * View the grading table of all submissions for this assignment
  1447. *
  1448. * @return string
  1449. */
  1450. private function view_grading_table() {
  1451. global $USER, $CFG;
  1452. // Include grading options form
  1453. require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
  1454. require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
  1455. require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
  1456. $o = '';
  1457. $links = array();
  1458. if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
  1459. has_capability('moodle/grade:viewall', $this->get_course_context())) {
  1460. $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
  1461. $links[$gradebookurl] = get_string('viewgradebook', 'assign');
  1462. }
  1463. if ($this->is_any_submission_plugin_enabled()) {
  1464. $downloadurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=downloadall';
  1465. $links[$downloadurl] = get_string('downloadall', 'assign');
  1466. }
  1467. $gradingactions = new url_select($links);
  1468. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  1469. $perpage = get_user_preferences('assign_perpage', 10);
  1470. $filter = get_user_preferences('assign_filter', '');
  1471. $controller = $gradingmanager->get_active_controller();
  1472. $showquickgrading = empty($controller);
  1473. if (optional_param('action', '', PARAM_ALPHA) == 'saveoptions') {
  1474. $quickgrading = optional_param('quickgrading', false, PARAM_BOOL);
  1475. set_user_preference('assign_quickgrading', $quickgrading);
  1476. }
  1477. $quickgrading = get_user_preferences('assign_quickgrading', false);
  1478. // print options for changing the filter and changing the number of results per page
  1479. $gradingoptionsform = new mod_assign_grading_options_form(null,
  1480. array('cm'=>$this->get_course_module()->id,
  1481. 'contextid'=>$this->context->id,
  1482. 'userid'=>$USER->id,
  1483. 'showquickgrading'=>$showquickgrading,
  1484. 'quickgrading'=>$quickgrading),
  1485. 'post', '',
  1486. array('class'=>'gradingoptionsform'));
  1487. $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
  1488. array('cm'=>$this->get_course_module()->id,
  1489. 'submissiondrafts'=>$this->get_instance()->submissiondrafts),
  1490. 'post', '',
  1491. array('class'=>'gradingbatchoperationsform'));
  1492. $gradingoptionsdata = new stdClass();
  1493. $gradingoptionsdata->perpage = $perpage;
  1494. $gradingoptionsdata->filter = $filter;
  1495. $gradingoptionsform->set_data($gradingoptionsdata);
  1496. // plagiarism update status apearring in the grading book
  1497. if (!empty($CFG->enableplagiarism)) {
  1498. /** Include plagiarismlib.php */
  1499. require_once($CFG->libdir . '/plagiarismlib.php');
  1500. plagiarism_update_status($this->get_course(), $this->get_course_module());
  1501. }
  1502. $actionformtext = $this->output->render($gradingactions);
  1503. $o .= $this->output->render(new assign_header($this->get_instance(),
  1504. $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), $actionformtext));
  1505. $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
  1506. // load and print the table of submissions
  1507. if ($showquickgrading && $quickgrading) {
  1508. $table = $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, true));
  1509. $quickgradingform = new mod_assign_quick_grading_form(null,
  1510. array('cm'=>$this->get_course_module()->id,
  1511. 'gradingtable'=>$table));
  1512. $o .= $this->output->render(new assign_form('quickgradingform', $quickgradingform));
  1513. } else {
  1514. $o .= $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, false));
  1515. }
  1516. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  1517. $users = array_keys($this->list_participants($currentgroup, true));
  1518. if (count($users) != 0) {
  1519. // if no enrolled user in a course then don't display the batch operations feature
  1520. $o .= $this->output->render(new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform));
  1521. }
  1522. $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform, 'M.mod_assign.init_grading_options'));
  1523. return $o;
  1524. }
  1525. /**
  1526. * View entire grading page.
  1527. *
  1528. * @return string
  1529. */
  1530. private function view_grading_page() {
  1531. global $CFG;
  1532. $o = '';
  1533. // Need submit permission to submit an assignment
  1534. require_capability('mod/assign:grade', $this->context);
  1535. require_once($CFG->dirroot . '/mod/assign/gradeform.php');
  1536. // only load this if it is
  1537. $o .= $this->view_grading_table();
  1538. $o .= $this->view_footer();
  1539. $this->add_to_log('view submission grading table', get_string('viewsubmissiongradingtable', 'assign'));
  1540. return $o;
  1541. }
  1542. /**
  1543. * Capture the output of the plagiarism plugins disclosures and return it as a string
  1544. *
  1545. * @return void
  1546. */
  1547. private function plagiarism_print_disclosure() {
  1548. global $CFG;
  1549. $o = '';
  1550. if (!empty($CFG->enableplagiarism)) {
  1551. /** Include plagiarismlib.php */
  1552. require_once($CFG->libdir . '/plagiarismlib.php');
  1553. ob_start();
  1554. plagiarism_print_disclosure($this->get_course_module()->id);
  1555. $o = ob_get_contents();
  1556. ob_end_clean();
  1557. }
  1558. return $o;
  1559. }
  1560. /**
  1561. * message for students when assignment submissions have been closed
  1562. *
  1563. * @return string
  1564. */
  1565. private function view_student_error_message() {
  1566. global $CFG;
  1567. $o = '';
  1568. // Need submit permission to submit an assignment
  1569. require_capability('mod/assign:submit', $this->context);
  1570. $o .= $this->output->render(new assign_header($this->get_instance(),
  1571. $this->get_context(),
  1572. $this->show_intro(),
  1573. $this->get_course_module()->id,
  1574. get_string('editsubmission', 'assign')));
  1575. $o .= $this->output->notification(get_string('submissionsclosed', 'assign'));
  1576. $o .= $this->view_footer();
  1577. return $o;
  1578. }
  1579. /**
  1580. * View edit submissions page.
  1581. *
  1582. * @param moodleform $mform
  1583. * @return void
  1584. */
  1585. private function view_edit_submission_page($mform) {
  1586. global $CFG;
  1587. $o = '';
  1588. // Include submission form
  1589. require_once($CFG->dirroot . '/mod/assign/submission_form.php');
  1590. // Need submit permission to submit an assignment
  1591. require_capability('mod/assign:submit', $this->context);
  1592. if (!$this->submissions_open()) {
  1593. return $this->view_student_error_message();
  1594. }
  1595. $o .= $this->output->render(new assign_header($this->get_instance(),
  1596. $this->get_context(),
  1597. $this->show_intro(),
  1598. $this->get_course_module()->id,
  1599. get_string('editsubmission', 'assign')));
  1600. $o .= $this->plagiarism_print_disclosure();
  1601. $data = new stdClass();
  1602. if (!$mform) {
  1603. $mform = new mod_assign_submission_form(null, array($this, $data));
  1604. }
  1605. $o .= $this->output->render(new assign_form('editsubmissionform',$mform));
  1606. $o .= $this->view_footer();
  1607. $this->add_to_log('view submit assignment form', get_string('viewownsubmissionform', 'assign'));
  1608. return $o;
  1609. }
  1610. /**
  1611. * See if this assignment has a grade yet
  1612. *
  1613. * @param int $userid
  1614. * @return bool
  1615. */
  1616. private function is_graded($userid) {
  1617. $grade = $this->get_user_grade($userid, false);
  1618. if ($grade) {
  1619. return ($grade->grade !== NULL && $grade->grade >= 0);
  1620. }
  1621. return false;
  1622. }
  1623. /**
  1624. * Perform an access check to see if the current $USER can view this users submission
  1625. *
  1626. * @param int $userid
  1627. * @return bool
  1628. */
  1629. public function can_view_submission($userid) {
  1630. global $USER;
  1631. if (!is_enrolled($this->get_course_context(), $userid)) {
  1632. return false;
  1633. }
  1634. if ($userid == $USER->id && !has_capability('mod/assign:submit', $this->context)) {
  1635. return false;
  1636. }
  1637. if ($userid != $USER->id && !has_capability('mod/assign:grade', $this->context)) {
  1638. return false;
  1639. }
  1640. return true;
  1641. }
  1642. /**
  1643. * Ask the user to confirm they want to perform this batch operation
  1644. * @return string
  1645. */
  1646. private function process_batch_grading_operation() {
  1647. global $CFG;
  1648. require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
  1649. require_sesskey();
  1650. $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
  1651. array('cm'=>$this->get_course_module()->id,
  1652. 'submissiondrafts'=>$this->get_instance()->submissiondrafts),
  1653. 'post', '',
  1654. array('class'=>'gradingbatchoperationsform'));
  1655. if ($data = $gradingbatchoperationsform->get_data()) {
  1656. // get the list of users
  1657. $users = $data->selectedusers;
  1658. $userlist = explode(',', $users);
  1659. foreach ($userlist as $userid) {
  1660. if ($data->operation == 'lock') {
  1661. $this->process_lock($userid);
  1662. } else if ($data->operation == 'unlock') {
  1663. $this->process_unlock($userid);
  1664. } else if ($data->operation == 'reverttodraft') {
  1665. $this->process_revert_to_draft($userid);
  1666. }
  1667. }
  1668. }
  1669. return true;
  1670. }
  1671. /**
  1672. * Ask the user to confirm they want to submit their work for grading
  1673. * @return string
  1674. */
  1675. private function check_submit_for_grading() {
  1676. global $USER;
  1677. // Check that all of the submission plugins are ready for this submission
  1678. $notifications = array();
  1679. $submission = $this->get_user_submission($USER->id, false);
  1680. $plugins = $this->get_submission_plugins();
  1681. foreach ($plugins as $plugin) {
  1682. if ($plugin->is_enabled() && $plugin->is_visible()) {
  1683. $check = $plugin->precheck_submission($submission);
  1684. if ($check !== true) {
  1685. $notifications[] = $check;
  1686. }
  1687. }
  1688. }
  1689. $o = '';
  1690. $o .= $this->output->header();
  1691. $o .= $this->output->render(new assign_submit_for_grading_page($notifications, $this->get_course_module()->id));
  1692. $o .= $this->view_footer();
  1693. return $o;
  1694. }
  1695. /**
  1696. * Print 2 tables of information with no action links -
  1697. * the submission summary and the grading summary
  1698. *
  1699. * @param stdClass $user the user to print the report for
  1700. * @param bool $showlinks - Return plain text or links to the profile
  1701. * @return string - the html summary
  1702. */
  1703. public function view_student_summary($user, $showlinks) {
  1704. global $CFG, $DB, $PAGE;
  1705. $grade = $this->get_user_grade($user->id, false);
  1706. $submission = $this->get_user_submission($user->id, false);
  1707. $o = '';
  1708. if ($this->can_view_submission($user->id)) {
  1709. $showedit = has_capability('mod/assign:submit', $this->context) &&
  1710. $this->submissions_open() && ($this->is_any_submission_plugin_enabled()) && $showlinks;
  1711. $showsubmit = $submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT) && $showlinks;
  1712. $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($user->id);
  1713. $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
  1714. $this->get_instance()->alwaysshowdescription,
  1715. $submission,
  1716. $this->is_any_submission_plugin_enabled(),
  1717. $gradelocked,
  1718. $this->is_graded($user->id),
  1719. $this->get_instance()->duedate,
  1720. $this->get_submission_plugins(),
  1721. $this->get_return_action(),
  1722. $this->get_return_params(),
  1723. $this->get_course_module()->id,
  1724. assign_submission_status::STUDENT_VIEW,
  1725. $showedit,
  1726. $showsubmit));
  1727. require_once($CFG->libdir.'/gradelib.php');
  1728. require_once($CFG->dirroot.'/grade/grading/lib.php');
  1729. $gradinginfo = grade_get_grades($this->get_course()->id,
  1730. 'mod',
  1731. 'assign',
  1732. $this->get_instance()->id,
  1733. $user->id);
  1734. $gradingitem = $gradinginfo->items[0];
  1735. $gradebookgrade = $gradingitem->grades[$user->id];
  1736. // check to see if all feedback plugins are empty
  1737. $emptyplugins = true;
  1738. if ($grade) {
  1739. foreach ($this->get_feedback_plugins() as $plugin) {
  1740. if ($plugin->is_visible() && $plugin->is_enabled()) {
  1741. if (!$plugin->is_empty($grade)) {
  1742. $emptyplugins = false;
  1743. }
  1744. }
  1745. }
  1746. }
  1747. if (!($gradebookgrade->hidden) && ($gradebookgrade->grade !== null || !$emptyplugins)) {
  1748. $gradefordisplay = '';
  1749. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  1750. if ($controller = $gradingmanager->get_active_controller()) {
  1751. $controller->set_grade_range(make_grades_menu($this->get_instance()->grade));
  1752. $gradefordisplay = $controller->render_grade($PAGE,
  1753. $grade->id,
  1754. $gradingitem,
  1755. $gradebookgrade->str_long_grade,
  1756. has_capability('mod/assign:grade', $this->get_context()));
  1757. } else {
  1758. $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
  1759. }
  1760. $gradeddate = $gradebookgrade->dategraded;
  1761. $grader = $DB->get_record('user', array('id'=>$gradebookgrade->usermodified));
  1762. $feedbackstatus = new assign_feedback_status($gradefordisplay,
  1763. $gradeddate,
  1764. $grader,
  1765. $this->get_feedback_plugins(),
  1766. $grade,
  1767. $this->get_course_module()->id,
  1768. $this->get_return_action(),
  1769. $this->get_return_params());
  1770. $o .= $this->output->render($feedbackstatus);
  1771. }
  1772. }
  1773. return $o;
  1774. }
  1775. /**
  1776. * View submissions page (contains details of current submission).
  1777. *
  1778. * @return string
  1779. */
  1780. private function view_submission_page() {
  1781. global $CFG, $DB, $USER, $PAGE;
  1782. $o = '';
  1783. $o .= $this->output->render(new assign_header($this->get_instance(),
  1784. $this->get_context(),
  1785. $this->show_intro(),
  1786. $this->get_course_module()->id));
  1787. if ($this->can_grade()) {
  1788. $o .= $this->output->render(new assign_grading_summary($this->count_participants(0),
  1789. $this->get_instance()->submissiondrafts,
  1790. $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
  1791. $this->is_any_submission_plugin_enabled(),
  1792. $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
  1793. $this->get_instance()->duedate,
  1794. $this->get_course_module()->id
  1795. ));
  1796. }
  1797. $grade = $this->get_user_grade($USER->id, false);
  1798. $submission = $this->get_user_submission($USER->id, false);
  1799. if ($this->can_view_submission($USER->id)) {
  1800. $o .= $this->view_student_summary($USER, true);
  1801. }
  1802. $o .= $this->view_footer();
  1803. $this->add_to_log('view', get_string('viewownsubmissionstatus', 'assign'));
  1804. return $o;
  1805. }
  1806. /**
  1807. * convert the final raw grade(s) in the grading table for the gradebook
  1808. *
  1809. * @param stdClass $grade
  1810. * @return array
  1811. */
  1812. private function convert_grade_for_gradebook(stdClass $grade) {
  1813. $gradebookgrade = array();
  1814. // trying to match those array keys in grade update function in gradelib.php
  1815. // with keys in th database table assign_grades
  1816. // starting around line 262
  1817. if ($grade->grade >= 0) {
  1818. $gradebookgrade['rawgrade'] = $grade->grade;
  1819. }
  1820. $gradebookgrade['userid'] = $grade->userid;
  1821. $gradebookgrade['usermodified'] = $grade->grader;
  1822. $gradebookgrade['datesubmitted'] = NULL;
  1823. $gradebookgrade['dategraded'] = $grade->timemodified;
  1824. if (isset($grade->feedbackformat)) {
  1825. $gradebookgrade['feedbackformat'] = $grade->feedbackformat;
  1826. }
  1827. if (isset($grade->feedbacktext)) {
  1828. $gradebookgrade['feedback'] = $grade->feedbacktext;
  1829. }
  1830. return $gradebookgrade;
  1831. }
  1832. /**
  1833. * convert submission details for the gradebook
  1834. *
  1835. * @param stdClass $submission
  1836. * @return array
  1837. */
  1838. private function convert_submission_for_gradebook(stdClass $submission) {
  1839. $gradebookgrade = array();
  1840. $gradebookgrade['userid'] = $submission->userid;
  1841. $gradebookgrade['usermodified'] = $submission->userid;
  1842. $gradebookgrade['datesubmitted'] = $submission->timemodified;
  1843. return $gradebookgrade;
  1844. }
  1845. /**
  1846. * update grades in the gradebook
  1847. *
  1848. * @param mixed $submission stdClass|null
  1849. * @param mixed $grade stdClass|null
  1850. * @return bool
  1851. */
  1852. private function gradebook_item_update($submission=NULL, $grade=NULL) {
  1853. if($submission != NULL){
  1854. $gradebookgrade = $this->convert_submission_for_gradebook($submission);
  1855. }else{
  1856. $gradebookgrade = $this->convert_grade_for_gradebook($grade);
  1857. }
  1858. // Grading is disabled, return.
  1859. if ($this->grading_disabled($gradebookgrade['userid'])) {
  1860. return false;
  1861. }
  1862. $assign = clone $this->get_instance();
  1863. $assign->cmidnumber = $this->get_course_module()->id;
  1864. return assign_grade_item_update($assign, $gradebookgrade);
  1865. }
  1866. /**
  1867. * update grades in the gradebook based on submission time
  1868. *
  1869. * @param stdClass $submission
  1870. * @param bool $updatetime
  1871. * @return bool
  1872. */
  1873. private function update_submission(stdClass $submission, $updatetime=true) {
  1874. global $DB;
  1875. if ($updatetime) {
  1876. $submission->timemodified = time();
  1877. }
  1878. $result= $DB->update_record('assign_submission', $submission);
  1879. if ($result) {
  1880. $this->gradebook_item_update($submission);
  1881. }
  1882. return $result;
  1883. }
  1884. /**
  1885. * Is this assignment open for submissions?
  1886. *
  1887. * Check the due date,
  1888. * prevent late submissions,
  1889. * has this person already submitted,
  1890. * is the assignment locked?
  1891. *
  1892. * @return bool
  1893. */
  1894. private function submissions_open() {
  1895. global $USER;
  1896. $time = time();
  1897. $dateopen = true;
  1898. if ($this->get_instance()->preventlatesubmissions && $this->get_instance()->duedate) {
  1899. $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $this->get_instance()->duedate);
  1900. } else {
  1901. $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
  1902. }
  1903. if (!$dateopen) {
  1904. return false;
  1905. }
  1906. // now check if this user has already submitted etc.
  1907. if (!is_enrolled($this->get_course_context(), $USER)) {
  1908. return false;
  1909. }
  1910. if ($submission = $this->get_user_submission($USER->id, false)) {
  1911. if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  1912. // drafts are tracked and the student has submitted the assignment
  1913. return false;
  1914. }
  1915. }
  1916. if ($grade = $this->get_user_grade($USER->id, false)) {
  1917. if ($grade->locked) {
  1918. return false;
  1919. }
  1920. }
  1921. if ($this->grading_disabled($USER->id)) {
  1922. return false;
  1923. }
  1924. return true;
  1925. }
  1926. /**
  1927. * render the files in file area
  1928. * @param string $component
  1929. * @param string $area
  1930. * @param int $submissionid
  1931. * @return string
  1932. */
  1933. public function render_area_files($component, $area, $submissionid) {
  1934. global $USER;
  1935. if (!$submissionid) {
  1936. $submission = $this->get_user_submission($USER->id,false);
  1937. $submissionid = $submission->id;
  1938. }
  1939. $fs = get_file_storage();
  1940. $browser = get_file_browser();
  1941. $files = $fs->get_area_files($this->get_context()->id, $component, $area , $submissionid , "timemodified", false);
  1942. return $this->output->assign_files($this->context, $submissionid, $area, $component);
  1943. }
  1944. /**
  1945. * Returns a list of teachers that should be grading given submission
  1946. *
  1947. * @param int $userid
  1948. * @return array
  1949. */
  1950. private function get_graders($userid) {
  1951. //potential graders
  1952. $potentialgraders = get_enrolled_users($this->context, "mod/assign:grade");
  1953. $graders = array();
  1954. if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) { // Separate groups are being used
  1955. if ($groups = groups_get_all_groups($this->get_course()->id, $userid)) { // Try to find all groups
  1956. foreach ($groups as $group) {
  1957. foreach ($potentialgraders as $grader) {
  1958. if ($grader->id == $userid) {
  1959. continue; // do not send self
  1960. }
  1961. if (groups_is_member($group->id, $grader->id)) {
  1962. $graders[$grader->id] = $grader;
  1963. }
  1964. }
  1965. }
  1966. } else {
  1967. // user not in group, try to find graders without group
  1968. foreach ($potentialgraders as $grader) {
  1969. if ($grader->id == $userid) {
  1970. continue; // do not send self
  1971. }
  1972. if (!groups_has_membership($this->get_course_module(), $grader->id)) {
  1973. $graders[$grader->id] = $grader;
  1974. }
  1975. }
  1976. }
  1977. } else {
  1978. foreach ($potentialgraders as $grader) {
  1979. if ($grader->id == $userid) {
  1980. continue; // do not send self
  1981. }
  1982. // must be enrolled
  1983. if (is_enrolled($this->get_course_context(), $grader->id)) {
  1984. $graders[$grader->id] = $grader;
  1985. }
  1986. }
  1987. }
  1988. return $graders;
  1989. }
  1990. /**
  1991. * Format a notification for plain text
  1992. *
  1993. * @param string $messagetype
  1994. * @param stdClass $info
  1995. * @param stdClass $course
  1996. * @param stdClass $context
  1997. * @param string $modulename
  1998. * @param string $assignmentname
  1999. */
  2000. private static function format_notification_message_text($messagetype, $info, $course, $context, $modulename, $assignmentname) {
  2001. $posttext = format_string($course->shortname, true, array('context' => $context->get_course_context())).' -> '.
  2002. $modulename.' -> '.
  2003. format_string($assignmentname, true, array('context' => $context))."\n";
  2004. $posttext .= '---------------------------------------------------------------------'."\n";
  2005. $posttext .= get_string($messagetype . 'text', "assign", $info)."\n";
  2006. $posttext .= "\n---------------------------------------------------------------------\n";
  2007. return $posttext;
  2008. }
  2009. /**
  2010. * Format a notification for HTML
  2011. *
  2012. * @param string $messagetype
  2013. * @param stdClass $info
  2014. * @param stdClass $course
  2015. * @param stdClass $context
  2016. * @param string $modulename
  2017. * @param stdClass $coursemodule
  2018. * @param string $assignmentname
  2019. * @param stdClass $info
  2020. */
  2021. private static function format_notification_message_html($messagetype, $info, $course, $context, $modulename, $coursemodule, $assignmentname) {
  2022. global $CFG;
  2023. $posthtml = '<p><font face="sans-serif">'.
  2024. '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.format_string($course->shortname, true, array('context' => $context->get_course_context())).'</a> ->'.
  2025. '<a href="'.$CFG->wwwroot.'/mod/assign/index.php?id='.$course->id.'">'.$modulename.'</a> ->'.
  2026. '<a href="'.$CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id.'">'.format_string($assignmentname, true, array('context' => $context)).'</a></font></p>';
  2027. $posthtml .= '<hr /><font face="sans-serif">';
  2028. $posthtml .= '<p>'.get_string($messagetype . 'html', 'assign', $info).'</p>';
  2029. $posthtml .= '</font><hr />';
  2030. return $posthtml;
  2031. }
  2032. /**
  2033. * Message someone about something (static so it can be called from cron)
  2034. *
  2035. * @param stdClass $userfrom
  2036. * @param stdClass $userto
  2037. * @param string $messagetype
  2038. * @param string $eventtype
  2039. * @param int $updatetime
  2040. * @param stdClass $coursemodule
  2041. * @param stdClass $context
  2042. * @param stdClass $course
  2043. * @param string $modulename
  2044. * @param string $assignmentname
  2045. * @return void
  2046. */
  2047. public static function send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
  2048. $updatetime, $coursemodule, $context, $course,
  2049. $modulename, $assignmentname) {
  2050. global $CFG;
  2051. $info = new stdClass();
  2052. $info->username = fullname($userfrom, true);
  2053. $info->assignment = format_string($assignmentname,true, array('context'=>$context));
  2054. $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
  2055. $info->timeupdated = strftime('%c',$updatetime);
  2056. $postsubject = get_string($messagetype . 'small', 'assign', $info);
  2057. $posttext = self::format_notification_message_text($messagetype, $info, $course, $context, $modulename, $assignmentname);
  2058. $posthtml = ($userto->mailformat == 1) ? self::format_notification_message_html($messagetype, $info, $course, $context, $modulename, $coursemodule, $assignmentname) : '';
  2059. $eventdata = new stdClass();
  2060. $eventdata->modulename = 'assign';
  2061. $eventdata->userfrom = $userfrom;
  2062. $eventdata->userto = $userto;
  2063. $eventdata->subject = $postsubject;
  2064. $eventdata->fullmessage = $posttext;
  2065. $eventdata->fullmessageformat = FORMAT_PLAIN;
  2066. $eventdata->fullmessagehtml = $posthtml;
  2067. $eventdata->smallmessage = $postsubject;
  2068. $eventdata->name = $eventtype;
  2069. $eventdata->component = 'mod_assign';
  2070. $eventdata->notification = 1;
  2071. $eventdata->contexturl = $info->url;
  2072. $eventdata->contexturlname = $info->assignment;
  2073. message_send($eventdata);
  2074. }
  2075. /**
  2076. * Message someone about something
  2077. *
  2078. * @param stdClass $userfrom
  2079. * @param stdClass $userto
  2080. * @param string $messagetype
  2081. * @param string $eventtype
  2082. * @param int $updatetime
  2083. * @return void
  2084. */
  2085. public function send_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime) {
  2086. self::send_assignment_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime, $this->get_course_module(), $this->get_context(), $this->get_course(), $this->get_module_name(), $this->get_instance()->name);
  2087. }
  2088. /**
  2089. * Notify student upon successful submission
  2090. *
  2091. * @global moodle_database $DB
  2092. * @param stdClass $submission
  2093. * @return void
  2094. */
  2095. private function notify_student_submission_receipt(stdClass $submission) {
  2096. global $DB;
  2097. $adminconfig = $this->get_admin_config();
  2098. if (!$adminconfig->submissionreceipts) {
  2099. // No need to do anything
  2100. return;
  2101. }
  2102. $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
  2103. $this->send_notification($user, $user, 'submissionreceipt', 'assign_notification', $submission->timemodified);
  2104. }
  2105. /**
  2106. * Send notifications to graders upon student submissions
  2107. *
  2108. * @global moodle_database $DB
  2109. * @param stdClass $submission
  2110. * @return void
  2111. */
  2112. private function notify_graders(stdClass $submission) {
  2113. global $DB;
  2114. $late = $this->get_instance()->duedate && ($this->get_instance()->duedate < time());
  2115. if (!$this->get_instance()->sendnotifications && !($late && $this->get_instance()->sendlatenotifications)) { // No need to do anything
  2116. return;
  2117. }
  2118. $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
  2119. if ($teachers = $this->get_graders($user->id)) {
  2120. foreach ($teachers as $teacher) {
  2121. $this->send_notification($user, $teacher, 'gradersubmissionupdated', 'assign_notification', $submission->timemodified);
  2122. }
  2123. }
  2124. }
  2125. /**
  2126. * assignment submission is processed before grading
  2127. *
  2128. * @return void
  2129. */
  2130. private function process_submit_for_grading() {
  2131. global $USER;
  2132. // Need submit permission to submit an assignment
  2133. require_capability('mod/assign:submit', $this->context);
  2134. require_sesskey();
  2135. $submission = $this->get_user_submission($USER->id,true);
  2136. if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  2137. // Give each submission plugin a chance to process the submission
  2138. $plugins = $this->get_submission_plugins();
  2139. foreach ($plugins as $plugin) {
  2140. $plugin->submit_for_grading();
  2141. }
  2142. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  2143. $this->update_submission($submission);
  2144. $this->add_to_log('submit for grading', $this->format_submission_for_log($submission));
  2145. $this->notify_graders($submission);
  2146. $this->notify_student_submission_receipt($submission);
  2147. }
  2148. }
  2149. /**
  2150. * save quick grades
  2151. *
  2152. * @global moodle_database $DB
  2153. * @return string The result of the save operation
  2154. */
  2155. private function process_save_quick_grades() {
  2156. global $USER, $DB, $CFG;
  2157. // Need grade permission
  2158. require_capability('mod/assign:grade', $this->context);
  2159. // make sure advanced grading is disabled
  2160. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  2161. $controller = $gradingmanager->get_active_controller();
  2162. if (!empty($controller)) {
  2163. return get_string('errorquickgradingvsadvancedgrading', 'assign');
  2164. }
  2165. $users = array();
  2166. // first check all the last modified values
  2167. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  2168. $participants = $this->list_participants($currentgroup, true);
  2169. // gets a list of possible users and look for values based upon that.
  2170. foreach ($participants as $userid => $unused) {
  2171. $modified = optional_param('grademodified_' . $userid, -1, PARAM_INT);
  2172. if ($modified >= 0) {
  2173. // gather the userid, updated grade and last modified value
  2174. $record = new stdClass();
  2175. $record->userid = $userid;
  2176. $record->grade = unformat_float(required_param('quickgrade_' . $record->userid, PARAM_TEXT));
  2177. $record->lastmodified = $modified;
  2178. $record->gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, array($userid));
  2179. $users[$userid] = $record;
  2180. }
  2181. }
  2182. if (empty($users)) {
  2183. // Quick check to see whether we have any users to update and we don't
  2184. return get_string('quickgradingchangessaved', 'assign'); // Technical lie
  2185. }
  2186. list($userids, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
  2187. $params['assignment'] = $this->get_instance()->id;
  2188. // check them all for currency
  2189. $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified
  2190. FROM {user} u
  2191. LEFT JOIN {assign_grades} g ON u.id = g.userid AND g.assignment = :assignment
  2192. WHERE u.id ' . $userids;
  2193. $currentgrades = $DB->get_recordset_sql($sql, $params);
  2194. $modifiedusers = array();
  2195. foreach ($currentgrades as $current) {
  2196. $modified = $users[(int)$current->userid];
  2197. $grade = $this->get_user_grade($userid, false);
  2198. // check to see if the outcomes were modified
  2199. if ($CFG->enableoutcomes) {
  2200. foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
  2201. $oldoutcome = $outcome->grades[$modified->userid]->grade;
  2202. $newoutcome = optional_param('outcome_' . $outcomeid . '_' . $modified->userid, -1, PARAM_FLOAT);
  2203. if ($oldoutcome != $newoutcome) {
  2204. // can't check modified time for outcomes because it is not reported
  2205. $modifiedusers[$modified->userid] = $modified;
  2206. continue;
  2207. }
  2208. }
  2209. }
  2210. // let plugins participate
  2211. foreach ($this->feedbackplugins as $plugin) {
  2212. if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
  2213. if ($plugin->is_quickgrading_modified($modified->userid, $grade)) {
  2214. if ((int)$current->lastmodified > (int)$modified->lastmodified) {
  2215. return get_string('errorrecordmodified', 'assign');
  2216. } else {
  2217. $modifiedusers[$modified->userid] = $modified;
  2218. continue;
  2219. }
  2220. }
  2221. }
  2222. }
  2223. if (($current->grade < 0 || $current->grade === NULL) &&
  2224. ($modified->grade < 0 || $modified->grade === NULL)) {
  2225. // different ways to indicate no grade
  2226. continue;
  2227. }
  2228. // Treat 0 and null as different values
  2229. if ($current->grade !== null) {
  2230. $current->grade = floatval($current->grade);
  2231. }
  2232. if ($current->grade !== $modified->grade) {
  2233. // grade changed
  2234. if ($this->grading_disabled($modified->userid)) {
  2235. continue;
  2236. }
  2237. if ((int)$current->lastmodified > (int)$modified->lastmodified) {
  2238. // error - record has been modified since viewing the page
  2239. return get_string('errorrecordmodified', 'assign');
  2240. } else {
  2241. $modifiedusers[$modified->userid] = $modified;
  2242. }
  2243. }
  2244. }
  2245. $currentgrades->close();
  2246. // ok - ready to process the updates
  2247. foreach ($modifiedusers as $userid => $modified) {
  2248. $grade = $this->get_user_grade($userid, true);
  2249. $grade->grade= grade_floatval(unformat_float($modified->grade));
  2250. $grade->grader= $USER->id;
  2251. $this->update_grade($grade);
  2252. // save plugins data
  2253. foreach ($this->feedbackplugins as $plugin) {
  2254. if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
  2255. $plugin->save_quickgrading_changes($userid, $grade);
  2256. }
  2257. }
  2258. // save outcomes
  2259. if ($CFG->enableoutcomes) {
  2260. $data = array();
  2261. foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
  2262. $oldoutcome = $outcome->grades[$modified->userid]->grade;
  2263. $newoutcome = optional_param('outcome_' . $outcomeid . '_' . $modified->userid, -1, PARAM_INT);
  2264. if ($oldoutcome != $newoutcome) {
  2265. $data[$outcomeid] = $newoutcome;
  2266. }
  2267. }
  2268. if (count($data) > 0) {
  2269. grade_update_outcomes('mod/assign', $this->course->id, 'mod', 'assign', $this->get_instance()->id, $userid, $data);
  2270. }
  2271. }
  2272. $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
  2273. }
  2274. return get_string('quickgradingchangessaved', 'assign');
  2275. }
  2276. /**
  2277. * save grading options
  2278. *
  2279. * @return void
  2280. */
  2281. private function process_save_grading_options() {
  2282. global $USER, $CFG;
  2283. // Include grading options form
  2284. require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
  2285. // Need submit permission to submit an assignment
  2286. require_capability('mod/assign:grade', $this->context);
  2287. $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id, 'contextid'=>$this->context->id, 'userid'=>$USER->id, 'showquickgrading'=>false));
  2288. if ($formdata = $mform->get_data()) {
  2289. set_user_preference('assign_perpage', $formdata->perpage);
  2290. set_user_preference('assign_filter', $formdata->filter);
  2291. }
  2292. }
  2293. /**
  2294. * Take a grade object and print a short summary for the log file.
  2295. * The size limit for the log file is 255 characters, so be careful not
  2296. * to include too much information.
  2297. *
  2298. * @param stdClass $grade
  2299. * @return string
  2300. */
  2301. private function format_grade_for_log(stdClass $grade) {
  2302. global $DB;
  2303. $user = $DB->get_record('user', array('id' => $grade->userid), '*', MUST_EXIST);
  2304. $info = get_string('gradestudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
  2305. if ($grade->grade != '') {
  2306. $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade, false) . '. ';
  2307. } else {
  2308. $info .= get_string('nograde', 'assign');
  2309. }
  2310. if ($grade->locked) {
  2311. $info .= get_string('submissionslocked', 'assign') . '. ';
  2312. }
  2313. return $info;
  2314. }
  2315. /**
  2316. * Take a submission object and print a short summary for the log file.
  2317. * The size limit for the log file is 255 characters, so be careful not
  2318. * to include too much information.
  2319. *
  2320. * @param stdClass $submission
  2321. * @return string
  2322. */
  2323. private function format_submission_for_log(stdClass $submission) {
  2324. $info = '';
  2325. $info .= get_string('submissionstatus', 'assign') . ': ' . get_string('submissionstatus_' . $submission->status, 'assign') . '. <br>';
  2326. // format_for_log here iterating every single log INFO from either submission or grade in every assignment plugin
  2327. foreach ($this->submissionplugins as $plugin) {
  2328. if ($plugin->is_enabled() && $plugin->is_visible()) {
  2329. $info .= "<br>" . $plugin->format_for_log($submission);
  2330. }
  2331. }
  2332. return $info;
  2333. }
  2334. /**
  2335. * save assignment submission
  2336. *
  2337. * @param moodleform $mform
  2338. * @return bool
  2339. */
  2340. private function process_save_submission(&$mform) {
  2341. global $USER, $CFG;
  2342. // Include submission form
  2343. require_once($CFG->dirroot . '/mod/assign/submission_form.php');
  2344. // Need submit permission to submit an assignment
  2345. require_capability('mod/assign:submit', $this->context);
  2346. require_sesskey();
  2347. $data = new stdClass();
  2348. $mform = new mod_assign_submission_form(null, array($this, $data));
  2349. if ($mform->is_cancelled()) {
  2350. return true;
  2351. }
  2352. if ($data = $mform->get_data()) {
  2353. $submission = $this->get_user_submission($USER->id, true); //create the submission if needed & its id
  2354. $grade = $this->get_user_grade($USER->id, false); // get the grade to check if it is locked
  2355. if ($grade && $grade->locked) {
  2356. print_error('submissionslocked', 'assign');
  2357. return true;
  2358. }
  2359. foreach ($this->submissionplugins as $plugin) {
  2360. if ($plugin->is_enabled()) {
  2361. if (!$plugin->save($submission, $data)) {
  2362. print_error($plugin->get_error());
  2363. }
  2364. }
  2365. }
  2366. $this->update_submission($submission);
  2367. // Logging
  2368. $this->add_to_log('submit', $this->format_submission_for_log($submission));
  2369. if (!$this->get_instance()->submissiondrafts) {
  2370. $this->notify_student_submission_receipt($submission);
  2371. $this->notify_graders($submission);
  2372. }
  2373. return true;
  2374. }
  2375. return false;
  2376. }
  2377. /**
  2378. * Determine if this users grade is locked or overridden
  2379. *
  2380. * @param int $userid - The student userid
  2381. * @return bool $gradingdisabled
  2382. */
  2383. public function grading_disabled($userid) {
  2384. global $CFG;
  2385. $gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, array($userid));
  2386. if (!$gradinginfo) {
  2387. return false;
  2388. }
  2389. if (!isset($gradinginfo->items[0]->grades[$userid])) {
  2390. return false;
  2391. }
  2392. $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
  2393. return $gradingdisabled;
  2394. }
  2395. /**
  2396. * Get an instance of a grading form if advanced grading is enabled
  2397. * This is specific to the assignment, marker and student
  2398. *
  2399. * @param int $userid - The student userid
  2400. * @param bool $gradingdisabled
  2401. * @return mixed gradingform_instance|null $gradinginstance
  2402. */
  2403. private function get_grading_instance($userid, $gradingdisabled) {
  2404. global $CFG, $USER;
  2405. $grade = $this->get_user_grade($userid, false);
  2406. $grademenu = make_grades_menu($this->get_instance()->grade);
  2407. $advancedgradingwarning = false;
  2408. $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
  2409. $gradinginstance = null;
  2410. if ($gradingmethod = $gradingmanager->get_active_method()) {
  2411. $controller = $gradingmanager->get_controller($gradingmethod);
  2412. if ($controller->is_form_available()) {
  2413. $itemid = null;
  2414. if ($grade) {
  2415. $itemid = $grade->id;
  2416. }
  2417. if ($gradingdisabled && $itemid) {
  2418. $gradinginstance = ($controller->get_current_instance($USER->id, $itemid));
  2419. } else if (!$gradingdisabled) {
  2420. $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
  2421. $gradinginstance = ($controller->get_or_create_instance($instanceid, $USER->id, $itemid));
  2422. }
  2423. } else {
  2424. $advancedgradingwarning = $controller->form_unavailable_notification();
  2425. }
  2426. }
  2427. if ($gradinginstance) {
  2428. $gradinginstance->get_controller()->set_grade_range($grademenu);
  2429. }
  2430. return $gradinginstance;
  2431. }
  2432. /**
  2433. * add elements to grade form
  2434. *
  2435. * @param MoodleQuickForm $mform
  2436. * @param stdClass $data
  2437. * @param array $params
  2438. * @return void
  2439. */
  2440. public function add_grade_form_elements(MoodleQuickForm $mform, stdClass $data, $params) {
  2441. global $USER, $CFG;
  2442. $settings = $this->get_instance();
  2443. $rownum = $params['rownum'];
  2444. $last = $params['last'];
  2445. $useridlist = $params['useridlist'];
  2446. $userid = $useridlist[$rownum];
  2447. $grade = $this->get_user_grade($userid, false);
  2448. // add advanced grading
  2449. $gradingdisabled = $this->grading_disabled($userid);
  2450. $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled);
  2451. if ($gradinginstance) {
  2452. $gradingelement = $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
  2453. if ($gradingdisabled) {
  2454. $gradingelement->freeze();
  2455. } else {
  2456. $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
  2457. }
  2458. } else {
  2459. // use simple direct grading
  2460. if ($this->get_instance()->grade > 0) {
  2461. $gradingelement = $mform->addElement('text', 'grade', get_string('gradeoutof', 'assign',$this->get_instance()->grade));
  2462. $mform->addHelpButton('grade', 'gradeoutofhelp', 'assign');
  2463. $mform->setType('grade', PARAM_TEXT);
  2464. if ($gradingdisabled) {
  2465. $gradingelement->freeze();
  2466. }
  2467. } else {
  2468. $grademenu = make_grades_menu($this->get_instance()->grade);
  2469. if (count($grademenu) > 0) {
  2470. $gradingelement = $mform->addElement('select', 'grade', get_string('grade').':', $grademenu);
  2471. $mform->setType('grade', PARAM_INT);
  2472. if ($gradingdisabled) {
  2473. $gradingelement->freeze();
  2474. }
  2475. }
  2476. }
  2477. }
  2478. $gradinginfo = grade_get_grades($this->get_course()->id,
  2479. 'mod',
  2480. 'assign',
  2481. $this->get_instance()->id,
  2482. $userid);
  2483. if (!empty($CFG->enableoutcomes)) {
  2484. foreach($gradinginfo->outcomes as $index=>$outcome) {
  2485. $options = make_grades_menu(-$outcome->scaleid);
  2486. if ($outcome->grades[$userid]->locked) {
  2487. $options[0] = get_string('nooutcome', 'grades');
  2488. $mform->addElement('static', 'outcome_'.$index.'['.$userid.']', $outcome->name.':',
  2489. $options[$outcome->grades[$userid]->grade]);
  2490. } else {
  2491. $options[''] = get_string('nooutcome', 'grades');
  2492. $attributes = array('id' => 'menuoutcome_'.$index );
  2493. $mform->addElement('select', 'outcome_'.$index.'['.$userid.']', $outcome->name.':', $options, $attributes );
  2494. $mform->setType('outcome_'.$index.'['.$userid.']', PARAM_INT);
  2495. $mform->setDefault('outcome_'.$index.'['.$userid.']', $outcome->grades[$userid]->grade );
  2496. }
  2497. }
  2498. }
  2499. if (has_all_capabilities(array('gradereport/grader:view', 'moodle/grade:viewall'), $this->get_course_context())) {
  2500. $grade = $this->output->action_link(new moodle_url('/grade/report/grader/index.php',
  2501. array('id'=>$this->get_course()->id)),
  2502. $gradinginfo->items[0]->grades[$userid]->str_grade);
  2503. } else {
  2504. $grade = $gradinginfo->items[0]->grades[$userid]->str_grade;
  2505. }
  2506. $mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assign').':' ,$grade);
  2507. $mform->addElement('static', 'progress', '', get_string('gradingstudentprogress', 'assign', array('index'=>$rownum+1, 'count'=>count($useridlist))));
  2508. // plugins
  2509. $this->add_plugin_grade_elements($grade, $mform, $data);
  2510. // hidden params
  2511. $mform->addElement('hidden', 'id', $this->get_course_module()->id);
  2512. $mform->setType('id', PARAM_INT);
  2513. $mform->addElement('hidden', 'rownum', $rownum);
  2514. $mform->setType('rownum', PARAM_INT);
  2515. $mform->addElement('hidden', 'useridlist', implode(',', $useridlist));
  2516. $mform->setType('useridlist', PARAM_TEXT);
  2517. $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
  2518. $mform->setType('ajax', PARAM_INT);
  2519. $mform->addElement('hidden', 'action', 'submitgrade');
  2520. $mform->setType('action', PARAM_ALPHA);
  2521. $buttonarray=array();
  2522. $buttonarray[] = $mform->createElement('submit', 'savegrade', get_string('savechanges', 'assign'));
  2523. if (!$last){
  2524. $buttonarray[] = $mform->createElement('submit', 'saveandshownext', get_string('savenext','assign'));
  2525. }
  2526. $buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
  2527. $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
  2528. $mform->closeHeaderBefore('buttonar');
  2529. $buttonarray=array();
  2530. if ($rownum > 0) {
  2531. $buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', get_string('previous','assign'));
  2532. }
  2533. if (!$last){
  2534. $buttonarray[] = $mform->createElement('submit', 'nosaveandnext', get_string('nosavebutnext', 'assign'));
  2535. }
  2536. $mform->addGroup($buttonarray, 'navar', '', array(' '), false);
  2537. }
  2538. /**
  2539. * add elements in submission plugin form
  2540. *
  2541. * @param mixed $submission stdClass|null
  2542. * @param MoodleQuickForm $mform
  2543. * @param stdClass $data
  2544. * @return void
  2545. */
  2546. private function add_plugin_submission_elements($submission, MoodleQuickForm $mform, stdClass $data) {
  2547. foreach ($this->submissionplugins as $plugin) {
  2548. if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
  2549. $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
  2550. if (!$plugin->get_form_elements($submission, $mform, $data)) {
  2551. $mform->removeElement('header_' . $plugin->get_type());
  2552. }
  2553. }
  2554. }
  2555. }
  2556. /**
  2557. * check if feedback plugins installed are enabled
  2558. *
  2559. * @return bool
  2560. */
  2561. public function is_any_feedback_plugin_enabled() {
  2562. if (!isset($this->cache['any_feedback_plugin_enabled'])) {
  2563. $this->cache['any_feedback_plugin_enabled'] = false;
  2564. foreach ($this->feedbackplugins as $plugin) {
  2565. if ($plugin->is_enabled() && $plugin->is_visible()) {
  2566. $this->cache['any_feedback_plugin_enabled'] = true;
  2567. break;
  2568. }
  2569. }
  2570. }
  2571. return $this->cache['any_feedback_plugin_enabled'];
  2572. }
  2573. /**
  2574. * check if submission plugins installed are enabled
  2575. *
  2576. * @return bool
  2577. */
  2578. public function is_any_submission_plugin_enabled() {
  2579. if (!isset($this->cache['any_submission_plugin_enabled'])) {
  2580. $this->cache['any_submission_plugin_enabled'] = false;
  2581. foreach ($this->submissionplugins as $plugin) {
  2582. if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
  2583. $this->cache['any_submission_plugin_enabled'] = true;
  2584. break;
  2585. }
  2586. }
  2587. }
  2588. return $this->cache['any_submission_plugin_enabled'];
  2589. }
  2590. /**
  2591. * add elements to submission form
  2592. * @param MoodleQuickForm $mform
  2593. * @param stdClass $data
  2594. * @return void
  2595. */
  2596. public function add_submission_form_elements(MoodleQuickForm $mform, stdClass $data) {
  2597. global $USER;
  2598. // online text submissions
  2599. $submission = $this->get_user_submission($USER->id, false);
  2600. $this->add_plugin_submission_elements($submission, $mform, $data);
  2601. // hidden params
  2602. $mform->addElement('hidden', 'id', $this->get_course_module()->id);
  2603. $mform->setType('id', PARAM_INT);
  2604. $mform->addElement('hidden', 'action', 'savesubmission');
  2605. $mform->setType('action', PARAM_TEXT);
  2606. // buttons
  2607. }
  2608. /**
  2609. * revert to draft
  2610. * Uses url parameter userid
  2611. *
  2612. * @param int $userid
  2613. * @return void
  2614. */
  2615. private function process_revert_to_draft($userid = 0) {
  2616. global $USER, $DB;
  2617. // Need grade permission
  2618. require_capability('mod/assign:grade', $this->context);
  2619. require_sesskey();
  2620. if (!$userid) {
  2621. $userid = required_param('userid', PARAM_INT);
  2622. }
  2623. $submission = $this->get_user_submission($userid, false);
  2624. if (!$submission) {
  2625. return;
  2626. }
  2627. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  2628. $this->update_submission($submission, false);
  2629. // update the modified time on the grade (grader modified)
  2630. $grade = $this->get_user_grade($userid, true);
  2631. $this->update_grade($grade);
  2632. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  2633. $this->add_to_log('revert submission to draft', get_string('reverttodraftforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
  2634. }
  2635. /**
  2636. * lock the process
  2637. * Uses url parameter userid
  2638. * @param int $userid
  2639. * @return void
  2640. */
  2641. private function process_lock($userid = 0) {
  2642. global $USER, $DB;
  2643. // Need grade permission
  2644. require_capability('mod/assign:grade', $this->context);
  2645. require_sesskey();
  2646. if (!$userid) {
  2647. $userid = required_param('userid', PARAM_INT);
  2648. }
  2649. $grade = $this->get_user_grade($userid, true);
  2650. $grade->locked = 1;
  2651. $grade->grader = $USER->id;
  2652. $this->update_grade($grade);
  2653. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  2654. $this->add_to_log('lock submission', get_string('locksubmissionforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
  2655. }
  2656. /**
  2657. * unlock the process
  2658. *
  2659. * @param int $userid
  2660. * @return void
  2661. */
  2662. private function process_unlock($userid = 0) {
  2663. global $USER, $DB;
  2664. // Need grade permission
  2665. require_capability('mod/assign:grade', $this->context);
  2666. require_sesskey();
  2667. if (!$userid) {
  2668. $userid = required_param('userid', PARAM_INT);
  2669. }
  2670. $grade = $this->get_user_grade($userid, true);
  2671. $grade->locked = 0;
  2672. $grade->grader = $USER->id;
  2673. $this->update_grade($grade);
  2674. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  2675. $this->add_to_log('unlock submission', get_string('unlocksubmissionforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
  2676. }
  2677. /**
  2678. * save outcomes submitted from grading form
  2679. *
  2680. * @param int $userid
  2681. * @param stdClass $formdata
  2682. */
  2683. private function process_outcomes($userid, $formdata) {
  2684. global $CFG, $USER;
  2685. if (empty($CFG->enableoutcomes)) {
  2686. return;
  2687. }
  2688. if ($this->grading_disabled($userid)) {
  2689. return;
  2690. }
  2691. require_once($CFG->libdir.'/gradelib.php');
  2692. $data = array();
  2693. $gradinginfo = grade_get_grades($this->get_course()->id,
  2694. 'mod',
  2695. 'assign',
  2696. $this->get_instance()->id,
  2697. $userid);
  2698. if (!empty($gradinginfo->outcomes)) {
  2699. foreach($gradinginfo->outcomes as $index=>$oldoutcome) {
  2700. $name = 'outcome_'.$index;
  2701. if (isset($formdata->{$name}[$userid]) and $oldoutcome->grades[$userid]->grade != $formdata->{$name}[$userid]) {
  2702. $data[$index] = $formdata->{$name}[$userid];
  2703. }
  2704. }
  2705. }
  2706. if (count($data) > 0) {
  2707. grade_update_outcomes('mod/assign', $this->course->id, 'mod', 'assign', $this->get_instance()->id, $userid, $data);
  2708. }
  2709. }
  2710. /**
  2711. * save grade
  2712. *
  2713. * @param moodleform $mform
  2714. * @return bool - was the grade saved
  2715. */
  2716. private function process_save_grade(&$mform) {
  2717. global $USER, $DB, $CFG;
  2718. // Include grade form
  2719. require_once($CFG->dirroot . '/mod/assign/gradeform.php');
  2720. // Need submit permission to submit an assignment
  2721. require_capability('mod/assign:grade', $this->context);
  2722. require_sesskey();
  2723. $rownum = required_param('rownum', PARAM_INT);
  2724. $useridlist = optional_param('useridlist', '', PARAM_TEXT);
  2725. if ($useridlist) {
  2726. $useridlist = explode(',', $useridlist);
  2727. } else {
  2728. $useridlist = $this->get_grading_userid_list();
  2729. }
  2730. $last = false;
  2731. $userid = $useridlist[$rownum];
  2732. if ($rownum == count($useridlist) - 1) {
  2733. $last = true;
  2734. }
  2735. $data = new stdClass();
  2736. $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>false)), 'post', '', array('class'=>'gradeform'));
  2737. if ($formdata = $mform->get_data()) {
  2738. $grade = $this->get_user_grade($userid, true);
  2739. $gradingdisabled = $this->grading_disabled($userid);
  2740. $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled);
  2741. if (!$gradingdisabled) {
  2742. if ($gradinginstance) {
  2743. $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
  2744. } else {
  2745. // handle the case when grade is set to No Grade
  2746. if (isset($formdata->grade)) {
  2747. $grade->grade = grade_floatval(unformat_float($formdata->grade));
  2748. }
  2749. }
  2750. }
  2751. $grade->grader= $USER->id;
  2752. $adminconfig = $this->get_admin_config();
  2753. $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
  2754. // call save in plugins
  2755. foreach ($this->feedbackplugins as $plugin) {
  2756. if ($plugin->is_enabled() && $plugin->is_visible()) {
  2757. if (!$plugin->save($grade, $formdata)) {
  2758. $result = false;
  2759. print_error($plugin->get_error());
  2760. }
  2761. if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
  2762. // this is the feedback plugin chose to push comments to the gradebook
  2763. $grade->feedbacktext = $plugin->text_for_gradebook($grade);
  2764. $grade->feedbackformat = $plugin->format_for_gradebook($grade);
  2765. }
  2766. }
  2767. }
  2768. $this->process_outcomes($userid, $formdata);
  2769. $grade->mailed = 0;
  2770. $this->update_grade($grade);
  2771. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  2772. $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
  2773. } else {
  2774. return false;
  2775. }
  2776. return true;
  2777. }
  2778. /**
  2779. * This function is a static wrapper around can_upgrade
  2780. *
  2781. * @param string $type The plugin type
  2782. * @param int $version The plugin version
  2783. * @return bool
  2784. */
  2785. public static function can_upgrade_assignment($type, $version) {
  2786. $assignment = new assign(null, null, null);
  2787. return $assignment->can_upgrade($type, $version);
  2788. }
  2789. /**
  2790. * This function returns true if it can upgrade an assignment from the 2.2
  2791. * module.
  2792. * @param string $type The plugin type
  2793. * @param int $version The plugin version
  2794. * @return bool
  2795. */
  2796. public function can_upgrade($type, $version) {
  2797. if ($type == 'offline' && $version >= 2011112900) {
  2798. return true;
  2799. }
  2800. foreach ($this->submissionplugins as $plugin) {
  2801. if ($plugin->can_upgrade($type, $version)) {
  2802. return true;
  2803. }
  2804. }
  2805. foreach ($this->feedbackplugins as $plugin) {
  2806. if ($plugin->can_upgrade($type, $version)) {
  2807. return true;
  2808. }
  2809. }
  2810. return false;
  2811. }
  2812. /**
  2813. * Copy all the files from the old assignment files area to the new one.
  2814. * This is used by the plugin upgrade code.
  2815. *
  2816. * @param int $oldcontextid The old assignment context id
  2817. * @param int $oldcomponent The old assignment component ('assignment')
  2818. * @param int $oldfilearea The old assignment filearea ('submissions')
  2819. * @param int $olditemid The old submissionid (can be null e.g. intro)
  2820. * @param int $newcontextid The new assignment context id
  2821. * @param int $newcomponent The new assignment component ('assignment')
  2822. * @param int $newfilearea The new assignment filearea ('submissions')
  2823. * @param int $newitemid The new submissionid (can be null e.g. intro)
  2824. * @return int The number of files copied
  2825. */
  2826. public function copy_area_files_for_upgrade($oldcontextid, $oldcomponent, $oldfilearea, $olditemid, $newcontextid, $newcomponent, $newfilearea, $newitemid) {
  2827. // Note, this code is based on some code in filestorage - but that code
  2828. // deleted the old files (which we don't want)
  2829. $count = 0;
  2830. $fs = get_file_storage();
  2831. $oldfiles = $fs->get_area_files($oldcontextid, $oldcomponent, $oldfilearea, $olditemid, 'id', false);
  2832. foreach ($oldfiles as $oldfile) {
  2833. $filerecord = new stdClass();
  2834. $filerecord->contextid = $newcontextid;
  2835. $filerecord->component = $newcomponent;
  2836. $filerecord->filearea = $newfilearea;
  2837. $filerecord->itemid = $newitemid;
  2838. $fs->create_file_from_storedfile($filerecord, $oldfile);
  2839. $count += 1;
  2840. }
  2841. return $count;
  2842. }
  2843. /**
  2844. * Get an upto date list of user grades and feedback for the gradebook
  2845. *
  2846. * @param int $userid int or 0 for all users
  2847. * @return array of grade data formated for the gradebook api
  2848. * The data required by the gradebook api is userid,
  2849. * rawgrade,
  2850. * feedback,
  2851. * feedbackformat,
  2852. * usermodified,
  2853. * dategraded,
  2854. * datesubmitted
  2855. */
  2856. public function get_user_grades_for_gradebook($userid) {
  2857. global $DB, $CFG;
  2858. $grades = array();
  2859. $assignmentid = $this->get_instance()->id;
  2860. $adminconfig = $this->get_admin_config();
  2861. $gradebookpluginname = $adminconfig->feedback_plugin_for_gradebook;
  2862. $gradebookplugin = null;
  2863. // find the gradebook plugin
  2864. foreach ($this->feedbackplugins as $plugin) {
  2865. if ($plugin->is_enabled() && $plugin->is_visible()) {
  2866. if (('assignfeedback_' . $plugin->get_type()) == $gradebookpluginname) {
  2867. $gradebookplugin = $plugin;
  2868. }
  2869. }
  2870. }
  2871. if ($userid) {
  2872. $where = ' WHERE u.id = ? ';
  2873. } else {
  2874. $where = ' WHERE u.id != ? ';
  2875. }
  2876. $graderesults = $DB->get_recordset_sql('SELECT u.id as userid, s.timemodified as datesubmitted, g.grade as rawgrade, g.timemodified as dategraded, g.grader as usermodified
  2877. FROM {user} u
  2878. LEFT JOIN {assign_submission} s ON u.id = s.userid and s.assignment = ?
  2879. LEFT JOIN {assign_grades} g ON u.id = g.userid and g.assignment = ?
  2880. ' . $where, array($assignmentid, $assignmentid, $userid));
  2881. foreach ($graderesults as $result) {
  2882. $gradebookgrade = clone $result;
  2883. // now get the feedback
  2884. if ($gradebookplugin) {
  2885. $grade = $this->get_user_grade($result->userid, false);
  2886. if ($grade) {
  2887. $gradebookgrade->feedbacktext = $gradebookplugin->text_for_gradebook($grade);
  2888. $gradebookgrade->feedbackformat = $gradebookplugin->format_for_gradebook($grade);
  2889. }
  2890. }
  2891. $grades[$gradebookgrade->userid] = $gradebookgrade;
  2892. }
  2893. $graderesults->close();
  2894. return $grades;
  2895. }
  2896. }