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

/mod/assign/locallib.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 7217 lines | 4865 code | 882 blank | 1470 comment | 964 complexity | 25f2d5a4f4869fd9c988edf0db8aaadd MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * 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. // Assignment submission statuses.
  27. define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened');
  28. define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
  29. define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
  30. // Search filters for grading page.
  31. define('ASSIGN_FILTER_SUBMITTED', 'submitted');
  32. define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
  33. define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
  34. // Reopen attempt methods.
  35. define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none');
  36. define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual');
  37. define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass');
  38. // Special value means allow unlimited attempts.
  39. define('ASSIGN_UNLIMITED_ATTEMPTS', -1);
  40. // Marking workflow states.
  41. define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked');
  42. define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking');
  43. define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview');
  44. define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview');
  45. define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease');
  46. define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released');
  47. require_once($CFG->libdir . '/accesslib.php');
  48. require_once($CFG->libdir . '/formslib.php');
  49. require_once($CFG->dirroot . '/repository/lib.php');
  50. require_once($CFG->dirroot . '/mod/assign/mod_form.php');
  51. require_once($CFG->libdir . '/gradelib.php');
  52. require_once($CFG->dirroot . '/grade/grading/lib.php');
  53. require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
  54. require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
  55. require_once($CFG->dirroot . '/mod/assign/renderable.php');
  56. require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
  57. require_once($CFG->libdir . '/eventslib.php');
  58. require_once($CFG->libdir . '/portfolio/caller.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 stdClass the grade_item record for this assign instance's primary grade item. */
  70. private $gradeitem;
  71. /** @var context the context of the course module for this assign instance
  72. * (or just the course if we are creating a new one)
  73. */
  74. private $context;
  75. /** @var stdClass the course this assign instance belongs to */
  76. private $course;
  77. /** @var stdClass the admin config for all assign instances */
  78. private $adminconfig;
  79. /** @var assign_renderer the custom renderer for this module */
  80. private $output;
  81. /** @var stdClass the course module for this assign instance */
  82. private $coursemodule;
  83. /** @var array cache for things like the coursemodule name or the scale menu -
  84. * only lives for a single request.
  85. */
  86. private $cache;
  87. /** @var array list of the installed submission plugins */
  88. private $submissionplugins;
  89. /** @var array list of the installed feedback plugins */
  90. private $feedbackplugins;
  91. /** @var string action to be used to return to this page
  92. * (without repeating any form submissions etc).
  93. */
  94. private $returnaction = 'view';
  95. /** @var array params to be used to return to this page */
  96. private $returnparams = array();
  97. /** @var string modulename prevents excessive calls to get_string */
  98. private static $modulename = null;
  99. /** @var string modulenameplural prevents excessive calls to get_string */
  100. private static $modulenameplural = null;
  101. /** @var array of marking workflow states for the current user */
  102. private $markingworkflowstates = null;
  103. /** @var bool whether to exclude users with inactive enrolment */
  104. private $showonlyactiveenrol = null;
  105. /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
  106. private $participants = array();
  107. /**
  108. * Constructor for the base assign class.
  109. *
  110. * @param mixed $coursemodulecontext context|null the course module context
  111. * (or the course context if the coursemodule has not been
  112. * created yet).
  113. * @param mixed $coursemodule the current course module if it was already loaded,
  114. * otherwise this class will load one from the context as required.
  115. * @param mixed $course the current course if it was already loaded,
  116. * otherwise this class will load one from the context as required.
  117. */
  118. public function __construct($coursemodulecontext, $coursemodule, $course) {
  119. $this->context = $coursemodulecontext;
  120. $this->coursemodule = $coursemodule;
  121. $this->course = $course;
  122. // Temporary cache only lives for a single request - used to reduce db lookups.
  123. $this->cache = array();
  124. $this->submissionplugins = $this->load_plugins('assignsubmission');
  125. $this->feedbackplugins = $this->load_plugins('assignfeedback');
  126. }
  127. /**
  128. * Set the action and parameters that can be used to return to the current page.
  129. *
  130. * @param string $action The action for the current page
  131. * @param array $params An array of name value pairs which form the parameters
  132. * to return to the current page.
  133. * @return void
  134. */
  135. public function register_return_link($action, $params) {
  136. global $PAGE;
  137. $params['action'] = $action;
  138. $currenturl = $PAGE->url;
  139. $currenturl->params($params);
  140. $PAGE->set_url($currenturl);
  141. }
  142. /**
  143. * Return an action that can be used to get back to the current page.
  144. *
  145. * @return string action
  146. */
  147. public function get_return_action() {
  148. global $PAGE;
  149. $params = $PAGE->url->params();
  150. if (!empty($params['action'])) {
  151. return $params['action'];
  152. }
  153. return '';
  154. }
  155. /**
  156. * Based on the current assignment settings should we display the intro.
  157. *
  158. * @return bool showintro
  159. */
  160. protected function show_intro() {
  161. if ($this->get_instance()->alwaysshowdescription ||
  162. time() > $this->get_instance()->allowsubmissionsfromdate) {
  163. return true;
  164. }
  165. return false;
  166. }
  167. /**
  168. * Return a list of parameters that can be used to get back to the current page.
  169. *
  170. * @return array params
  171. */
  172. public function get_return_params() {
  173. global $PAGE;
  174. $params = $PAGE->url->params();
  175. unset($params['id']);
  176. unset($params['action']);
  177. return $params;
  178. }
  179. /**
  180. * Set the submitted form data.
  181. *
  182. * @param stdClass $data The form data (instance)
  183. */
  184. public function set_instance(stdClass $data) {
  185. $this->instance = $data;
  186. }
  187. /**
  188. * Set the context.
  189. *
  190. * @param context $context The new context
  191. */
  192. public function set_context(context $context) {
  193. $this->context = $context;
  194. }
  195. /**
  196. * Set the course data.
  197. *
  198. * @param stdClass $course The course data
  199. */
  200. public function set_course(stdClass $course) {
  201. $this->course = $course;
  202. }
  203. /**
  204. * Get list of feedback plugins installed.
  205. *
  206. * @return array
  207. */
  208. public function get_feedback_plugins() {
  209. return $this->feedbackplugins;
  210. }
  211. /**
  212. * Get list of submission plugins installed.
  213. *
  214. * @return array
  215. */
  216. public function get_submission_plugins() {
  217. return $this->submissionplugins;
  218. }
  219. /**
  220. * Is blind marking enabled and reveal identities not set yet?
  221. *
  222. * @return bool
  223. */
  224. public function is_blind_marking() {
  225. return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
  226. }
  227. /**
  228. * Does an assignment have submission(s) or grade(s) already?
  229. *
  230. * @return bool
  231. */
  232. public function has_submissions_or_grades() {
  233. $allgrades = $this->count_grades();
  234. $allsubmissions = $this->count_submissions();
  235. if (($allgrades == 0) && ($allsubmissions == 0)) {
  236. return false;
  237. }
  238. return true;
  239. }
  240. /**
  241. * Get a specific submission plugin by its type.
  242. *
  243. * @param string $subtype assignsubmission | assignfeedback
  244. * @param string $type
  245. * @return mixed assign_plugin|null
  246. */
  247. public function get_plugin_by_type($subtype, $type) {
  248. $shortsubtype = substr($subtype, strlen('assign'));
  249. $name = $shortsubtype . 'plugins';
  250. if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
  251. return null;
  252. }
  253. $pluginlist = $this->$name;
  254. foreach ($pluginlist as $plugin) {
  255. if ($plugin->get_type() == $type) {
  256. return $plugin;
  257. }
  258. }
  259. return null;
  260. }
  261. /**
  262. * Get a feedback plugin by type.
  263. *
  264. * @param string $type - The type of plugin e.g comments
  265. * @return mixed assign_feedback_plugin|null
  266. */
  267. public function get_feedback_plugin_by_type($type) {
  268. return $this->get_plugin_by_type('assignfeedback', $type);
  269. }
  270. /**
  271. * Get a submission plugin by type.
  272. *
  273. * @param string $type - The type of plugin e.g comments
  274. * @return mixed assign_submission_plugin|null
  275. */
  276. public function get_submission_plugin_by_type($type) {
  277. return $this->get_plugin_by_type('assignsubmission', $type);
  278. }
  279. /**
  280. * Load the plugins from the sub folders under subtype.
  281. *
  282. * @param string $subtype - either submission or feedback
  283. * @return array - The sorted list of plugins
  284. */
  285. protected function load_plugins($subtype) {
  286. global $CFG;
  287. $result = array();
  288. $names = core_component::get_plugin_list($subtype);
  289. foreach ($names as $name => $path) {
  290. if (file_exists($path . '/locallib.php')) {
  291. require_once($path . '/locallib.php');
  292. $shortsubtype = substr($subtype, strlen('assign'));
  293. $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
  294. $plugin = new $pluginclass($this, $name);
  295. if ($plugin instanceof assign_plugin) {
  296. $idx = $plugin->get_sort_order();
  297. while (array_key_exists($idx, $result)) {
  298. $idx +=1;
  299. }
  300. $result[$idx] = $plugin;
  301. }
  302. }
  303. }
  304. ksort($result);
  305. return $result;
  306. }
  307. /**
  308. * Display the assignment, used by view.php
  309. *
  310. * The assignment is displayed differently depending on your role,
  311. * the settings for the assignment and the status of the assignment.
  312. *
  313. * @param string $action The current action if any.
  314. * @return string - The page output.
  315. */
  316. public function view($action='') {
  317. $o = '';
  318. $mform = null;
  319. $notices = array();
  320. $nextpageparams = array();
  321. if (!empty($this->get_course_module()->id)) {
  322. $nextpageparams['id'] = $this->get_course_module()->id;
  323. }
  324. // Handle form submissions first.
  325. if ($action == 'savesubmission') {
  326. $action = 'editsubmission';
  327. if ($this->process_save_submission($mform, $notices)) {
  328. $action = 'redirect';
  329. $nextpageparams['action'] = 'view';
  330. }
  331. } else if ($action == 'editprevioussubmission') {
  332. $action = 'editsubmission';
  333. if ($this->process_copy_previous_attempt($notices)) {
  334. $action = 'redirect';
  335. $nextpageparams['action'] = 'editsubmission';
  336. }
  337. } else if ($action == 'lock') {
  338. $this->process_lock_submission();
  339. $action = 'redirect';
  340. $nextpageparams['action'] = 'grading';
  341. } else if ($action == 'addattempt') {
  342. $this->process_add_attempt(required_param('userid', PARAM_INT));
  343. $action = 'redirect';
  344. $nextpageparams['action'] = 'grading';
  345. } else if ($action == 'reverttodraft') {
  346. $this->process_revert_to_draft();
  347. $action = 'redirect';
  348. $nextpageparams['action'] = 'grading';
  349. } else if ($action == 'unlock') {
  350. $this->process_unlock_submission();
  351. $action = 'redirect';
  352. $nextpageparams['action'] = 'grading';
  353. } else if ($action == 'setbatchmarkingworkflowstate') {
  354. $this->process_set_batch_marking_workflow_state();
  355. $action = 'redirect';
  356. $nextpageparams['action'] = 'grading';
  357. } else if ($action == 'setbatchmarkingallocation') {
  358. $this->process_set_batch_marking_allocation();
  359. $action = 'redirect';
  360. $nextpageparams['action'] = 'grading';
  361. } else if ($action == 'confirmsubmit') {
  362. $action = 'submit';
  363. if ($this->process_submit_for_grading($mform)) {
  364. $action = 'redirect';
  365. $nextpageparams['action'] = 'view';
  366. }
  367. } else if ($action == 'gradingbatchoperation') {
  368. $action = $this->process_grading_batch_operation($mform);
  369. if ($action == 'grading') {
  370. $action = 'redirect';
  371. $nextpageparams['action'] = 'grading';
  372. }
  373. } else if ($action == 'submitgrade') {
  374. if (optional_param('saveandshownext', null, PARAM_RAW)) {
  375. // Save and show next.
  376. $action = 'grade';
  377. if ($this->process_save_grade($mform)) {
  378. $action = 'redirect';
  379. $nextpageparams['action'] = 'grade';
  380. $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
  381. $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
  382. }
  383. } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
  384. $action = 'redirect';
  385. $nextpageparams['action'] = 'grade';
  386. $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
  387. $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
  388. } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
  389. $action = 'redirect';
  390. $nextpageparams['action'] = 'grade';
  391. $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
  392. $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
  393. } else if (optional_param('savegrade', null, PARAM_RAW)) {
  394. // Save changes button.
  395. $action = 'grade';
  396. if ($this->process_save_grade($mform)) {
  397. $action = 'redirect';
  398. $nextpageparams['action'] = 'savegradingresult';
  399. }
  400. } else {
  401. // Cancel button.
  402. $action = 'redirect';
  403. $nextpageparams['action'] = 'grading';
  404. }
  405. } else if ($action == 'quickgrade') {
  406. $message = $this->process_save_quick_grades();
  407. $action = 'quickgradingresult';
  408. } else if ($action == 'saveoptions') {
  409. $this->process_save_grading_options();
  410. $action = 'redirect';
  411. $nextpageparams['action'] = 'grading';
  412. } else if ($action == 'saveextension') {
  413. $action = 'grantextension';
  414. if ($this->process_save_extension($mform)) {
  415. $action = 'redirect';
  416. $nextpageparams['action'] = 'grading';
  417. }
  418. } else if ($action == 'revealidentitiesconfirm') {
  419. $this->process_reveal_identities();
  420. $action = 'redirect';
  421. $nextpageparams['action'] = 'grading';
  422. }
  423. $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
  424. 'useridlistid'=>optional_param('useridlistid', 0, PARAM_INT));
  425. $this->register_return_link($action, $returnparams);
  426. // Now show the right view page.
  427. if ($action == 'redirect') {
  428. $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
  429. redirect($nextpageurl);
  430. return;
  431. } else if ($action == 'savegradingresult') {
  432. $message = get_string('gradingchangessaved', 'assign');
  433. $o .= $this->view_savegrading_result($message);
  434. } else if ($action == 'quickgradingresult') {
  435. $mform = null;
  436. $o .= $this->view_quickgrading_result($message);
  437. } else if ($action == 'grade') {
  438. $o .= $this->view_single_grade_page($mform);
  439. } else if ($action == 'viewpluginassignfeedback') {
  440. $o .= $this->view_plugin_content('assignfeedback');
  441. } else if ($action == 'viewpluginassignsubmission') {
  442. $o .= $this->view_plugin_content('assignsubmission');
  443. } else if ($action == 'editsubmission') {
  444. $o .= $this->view_edit_submission_page($mform, $notices);
  445. } else if ($action == 'grading') {
  446. $o .= $this->view_grading_page();
  447. } else if ($action == 'downloadall') {
  448. $o .= $this->download_submissions();
  449. } else if ($action == 'submit') {
  450. $o .= $this->check_submit_for_grading($mform);
  451. } else if ($action == 'grantextension') {
  452. $o .= $this->view_grant_extension($mform);
  453. } else if ($action == 'revealidentities') {
  454. $o .= $this->view_reveal_identities_confirm($mform);
  455. } else if ($action == 'plugingradingbatchoperation') {
  456. $o .= $this->view_plugin_grading_batch_operation($mform);
  457. } else if ($action == 'viewpluginpage') {
  458. $o .= $this->view_plugin_page();
  459. } else if ($action == 'viewcourseindex') {
  460. $o .= $this->view_course_index();
  461. } else if ($action == 'viewbatchsetmarkingworkflowstate') {
  462. $o .= $this->view_batch_set_workflow_state($mform);
  463. } else if ($action == 'viewbatchmarkingallocation') {
  464. $o .= $this->view_batch_markingallocation($mform);
  465. } else {
  466. $o .= $this->view_submission_page();
  467. }
  468. return $o;
  469. }
  470. /**
  471. * Add this instance to the database.
  472. *
  473. * @param stdClass $formdata The data submitted from the form
  474. * @param bool $callplugins This is used to skip the plugin code
  475. * when upgrading an old assignment to a new one (the plugins get called manually)
  476. * @return mixed false if an error occurs or the int id of the new instance
  477. */
  478. public function add_instance(stdClass $formdata, $callplugins) {
  479. global $DB;
  480. $err = '';
  481. // Add the database record.
  482. $update = new stdClass();
  483. $update->name = $formdata->name;
  484. $update->timemodified = time();
  485. $update->timecreated = time();
  486. $update->course = $formdata->course;
  487. $update->courseid = $formdata->course;
  488. $update->intro = $formdata->intro;
  489. $update->introformat = $formdata->introformat;
  490. $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
  491. $update->submissiondrafts = $formdata->submissiondrafts;
  492. $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
  493. $update->sendnotifications = $formdata->sendnotifications;
  494. $update->sendlatenotifications = $formdata->sendlatenotifications;
  495. $update->duedate = $formdata->duedate;
  496. $update->cutoffdate = $formdata->cutoffdate;
  497. $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
  498. $update->grade = $formdata->grade;
  499. $update->completionsubmit = !empty($formdata->completionsubmit);
  500. $update->teamsubmission = $formdata->teamsubmission;
  501. $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
  502. if (isset($formdata->teamsubmissiongroupingid)) {
  503. $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
  504. }
  505. $update->blindmarking = $formdata->blindmarking;
  506. $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
  507. if (!empty($formdata->attemptreopenmethod)) {
  508. $update->attemptreopenmethod = $formdata->attemptreopenmethod;
  509. }
  510. if (!empty($formdata->maxattempts)) {
  511. $update->maxattempts = $formdata->maxattempts;
  512. }
  513. $update->markingworkflow = $formdata->markingworkflow;
  514. $update->markingallocation = $formdata->markingallocation;
  515. if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
  516. $update->markingallocation = 0;
  517. }
  518. $returnid = $DB->insert_record('assign', $update);
  519. $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
  520. // Cache the course record.
  521. $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
  522. if ($callplugins) {
  523. // Call save_settings hook for submission plugins.
  524. foreach ($this->submissionplugins as $plugin) {
  525. if (!$this->update_plugin_instance($plugin, $formdata)) {
  526. print_error($plugin->get_error());
  527. return false;
  528. }
  529. }
  530. foreach ($this->feedbackplugins as $plugin) {
  531. if (!$this->update_plugin_instance($plugin, $formdata)) {
  532. print_error($plugin->get_error());
  533. return false;
  534. }
  535. }
  536. // In the case of upgrades the coursemodule has not been set,
  537. // so we need to wait before calling these two.
  538. $this->update_calendar($formdata->coursemodule);
  539. $this->update_gradebook(false, $formdata->coursemodule);
  540. }
  541. $update = new stdClass();
  542. $update->id = $this->get_instance()->id;
  543. $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
  544. $DB->update_record('assign', $update);
  545. return $returnid;
  546. }
  547. /**
  548. * Delete all grades from the gradebook for this assignment.
  549. *
  550. * @return bool
  551. */
  552. protected function delete_grades() {
  553. global $CFG;
  554. $result = grade_update('mod/assign',
  555. $this->get_course()->id,
  556. 'mod',
  557. 'assign',
  558. $this->get_instance()->id,
  559. 0,
  560. null,
  561. array('deleted'=>1));
  562. return $result == GRADE_UPDATE_OK;
  563. }
  564. /**
  565. * Delete this instance from the database.
  566. *
  567. * @return bool false if an error occurs
  568. */
  569. public function delete_instance() {
  570. global $DB;
  571. $result = true;
  572. foreach ($this->submissionplugins as $plugin) {
  573. if (!$plugin->delete_instance()) {
  574. print_error($plugin->get_error());
  575. $result = false;
  576. }
  577. }
  578. foreach ($this->feedbackplugins as $plugin) {
  579. if (!$plugin->delete_instance()) {
  580. print_error($plugin->get_error());
  581. $result = false;
  582. }
  583. }
  584. // Delete files associated with this assignment.
  585. $fs = get_file_storage();
  586. if (! $fs->delete_area_files($this->context->id) ) {
  587. $result = false;
  588. }
  589. // Delete_records will throw an exception if it fails - so no need for error checking here.
  590. $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
  591. $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
  592. $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
  593. // Delete items from the gradebook.
  594. if (! $this->delete_grades()) {
  595. $result = false;
  596. }
  597. // Delete the instance.
  598. $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
  599. return $result;
  600. }
  601. /**
  602. * Actual implementation of the reset course functionality, delete all the
  603. * assignment submissions for course $data->courseid.
  604. *
  605. * @param stdClass $data the data submitted from the reset course.
  606. * @return array status array
  607. */
  608. public function reset_userdata($data) {
  609. global $CFG, $DB;
  610. $componentstr = get_string('modulenameplural', 'assign');
  611. $status = array();
  612. $fs = get_file_storage();
  613. if (!empty($data->reset_assign_submissions)) {
  614. // Delete files associated with this assignment.
  615. foreach ($this->submissionplugins as $plugin) {
  616. $fileareas = array();
  617. $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
  618. $fileareas = $plugin->get_file_areas();
  619. foreach ($fileareas as $filearea) {
  620. $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
  621. }
  622. if (!$plugin->delete_instance()) {
  623. $status[] = array('component'=>$componentstr,
  624. 'item'=>get_string('deleteallsubmissions', 'assign'),
  625. 'error'=>$plugin->get_error());
  626. }
  627. }
  628. foreach ($this->feedbackplugins as $plugin) {
  629. $fileareas = array();
  630. $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
  631. $fileareas = $plugin->get_file_areas();
  632. foreach ($fileareas as $filearea) {
  633. $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
  634. }
  635. if (!$plugin->delete_instance()) {
  636. $status[] = array('component'=>$componentstr,
  637. 'item'=>get_string('deleteallsubmissions', 'assign'),
  638. 'error'=>$plugin->get_error());
  639. }
  640. }
  641. $assignssql = 'SELECT a.id
  642. FROM {assign} a
  643. WHERE a.course=:course';
  644. $params = array('course'=>$data->courseid);
  645. $DB->delete_records_select('assign_submission', "assignment IN ($assignssql)", $params);
  646. $status[] = array('component'=>$componentstr,
  647. 'item'=>get_string('deleteallsubmissions', 'assign'),
  648. 'error'=>false);
  649. if (!empty($data->reset_gradebook_grades)) {
  650. $DB->delete_records_select('assign_grades', "assignment IN ($assignssql)", $params);
  651. // Remove all grades from gradebook.
  652. require_once($CFG->dirroot.'/mod/assign/lib.php');
  653. assign_reset_gradebook($data->courseid);
  654. }
  655. }
  656. // Updating dates - shift may be negative too.
  657. if ($data->timeshift) {
  658. shift_course_mod_dates('assign',
  659. array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
  660. $data->timeshift,
  661. $data->courseid, $this->get_instance()->id);
  662. $status[] = array('component'=>$componentstr,
  663. 'item'=>get_string('datechanged'),
  664. 'error'=>false);
  665. }
  666. return $status;
  667. }
  668. /**
  669. * Update the settings for a single plugin.
  670. *
  671. * @param assign_plugin $plugin The plugin to update
  672. * @param stdClass $formdata The form data
  673. * @return bool false if an error occurs
  674. */
  675. protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
  676. if ($plugin->is_visible()) {
  677. $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
  678. if (!empty($formdata->$enabledname)) {
  679. $plugin->enable();
  680. if (!$plugin->save_settings($formdata)) {
  681. print_error($plugin->get_error());
  682. return false;
  683. }
  684. } else {
  685. $plugin->disable();
  686. }
  687. }
  688. return true;
  689. }
  690. /**
  691. * Update the gradebook information for this assignment.
  692. *
  693. * @param bool $reset If true, will reset all grades in the gradbook for this assignment
  694. * @param int $coursemoduleid This is required because it might not exist in the database yet
  695. * @return bool
  696. */
  697. public function update_gradebook($reset, $coursemoduleid) {
  698. global $CFG;
  699. require_once($CFG->dirroot.'/mod/assign/lib.php');
  700. $assign = clone $this->get_instance();
  701. $assign->cmidnumber = $coursemoduleid;
  702. $param = null;
  703. if ($reset) {
  704. $param = 'reset';
  705. }
  706. return assign_grade_item_update($assign, $param);
  707. }
  708. /**
  709. * Load and cache the admin config for this module.
  710. *
  711. * @return stdClass the plugin config
  712. */
  713. public function get_admin_config() {
  714. if ($this->adminconfig) {
  715. return $this->adminconfig;
  716. }
  717. $this->adminconfig = get_config('assign');
  718. return $this->adminconfig;
  719. }
  720. /**
  721. * Update the calendar entries for this assignment.
  722. *
  723. * @param int $coursemoduleid - Required to pass this in because it might
  724. * not exist in the database yet.
  725. * @return bool
  726. */
  727. public function update_calendar($coursemoduleid) {
  728. global $DB, $CFG;
  729. require_once($CFG->dirroot.'/calendar/lib.php');
  730. // Special case for add_instance as the coursemodule has not been set yet.
  731. $instance = $this->get_instance();
  732. if ($instance->duedate) {
  733. $event = new stdClass();
  734. $params = array('modulename'=>'assign', 'instance'=>$instance->id);
  735. $event->id = $DB->get_field('event', 'id', $params);
  736. $event->name = $instance->name;
  737. $event->timestart = $instance->duedate;
  738. // Convert the links to pluginfile. It is a bit hacky but at this stage the files
  739. // might not have been saved in the module area yet.
  740. $intro = $instance->intro;
  741. if ($draftid = file_get_submitted_draft_itemid('introeditor')) {
  742. $intro = file_rewrite_urls_to_pluginfile($intro, $draftid);
  743. }
  744. // We need to remove the links to files as the calendar is not ready
  745. // to support module events with file areas.
  746. $intro = strip_pluginfile_content($intro);
  747. if ($this->show_intro()) {
  748. $event->description = array(
  749. 'text' => $intro,
  750. 'format' => $instance->introformat
  751. );
  752. } else {
  753. $event->description = array(
  754. 'text' => '',
  755. 'format' => $instance->introformat
  756. );
  757. }
  758. if ($event->id) {
  759. $calendarevent = calendar_event::load($event->id);
  760. $calendarevent->update($event);
  761. } else {
  762. unset($event->id);
  763. $event->courseid = $instance->course;
  764. $event->groupid = 0;
  765. $event->userid = 0;
  766. $event->modulename = 'assign';
  767. $event->instance = $instance->id;
  768. $event->eventtype = 'due';
  769. $event->timeduration = 0;
  770. calendar_event::create($event);
  771. }
  772. } else {
  773. $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$instance->id));
  774. }
  775. }
  776. /**
  777. * Update this instance in the database.
  778. *
  779. * @param stdClass $formdata - the data submitted from the form
  780. * @return bool false if an error occurs
  781. */
  782. public function update_instance($formdata) {
  783. global $DB;
  784. $update = new stdClass();
  785. $update->id = $formdata->instance;
  786. $update->name = $formdata->name;
  787. $update->timemodified = time();
  788. $update->course = $formdata->course;
  789. $update->intro = $formdata->intro;
  790. $update->introformat = $formdata->introformat;
  791. $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
  792. $update->submissiondrafts = $formdata->submissiondrafts;
  793. $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
  794. $update->sendnotifications = $formdata->sendnotifications;
  795. $update->sendlatenotifications = $formdata->sendlatenotifications;
  796. $update->duedate = $formdata->duedate;
  797. $update->cutoffdate = $formdata->cutoffdate;
  798. $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
  799. $update->grade = $formdata->grade;
  800. if (!empty($formdata->completionunlocked)) {
  801. $update->completionsubmit = !empty($formdata->completionsubmit);
  802. }
  803. $update->teamsubmission = $formdata->teamsubmission;
  804. $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
  805. if (isset($formdata->teamsubmissiongroupingid)) {
  806. $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
  807. }
  808. $update->blindmarking = $formdata->blindmarking;
  809. $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
  810. if (!empty($formdata->attemptreopenmethod)) {
  811. $update->attemptreopenmethod = $formdata->attemptreopenmethod;
  812. }
  813. if (!empty($formdata->maxattempts)) {
  814. $update->maxattempts = $formdata->maxattempts;
  815. }
  816. $update->markingworkflow = $formdata->markingworkflow;
  817. $update->markingallocation = $formdata->markingallocation;
  818. if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
  819. $update->markingallocation = 0;
  820. }
  821. $result = $DB->update_record('assign', $update);
  822. $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
  823. // Load the assignment so the plugins have access to it.
  824. // Call save_settings hook for submission plugins.
  825. foreach ($this->submissionplugins as $plugin) {
  826. if (!$this->update_plugin_instance($plugin, $formdata)) {
  827. print_error($plugin->get_error());
  828. return false;
  829. }
  830. }
  831. foreach ($this->feedbackplugins as $plugin) {
  832. if (!$this->update_plugin_instance($plugin, $formdata)) {
  833. print_error($plugin->get_error());
  834. return false;
  835. }
  836. }
  837. $this->update_calendar($this->get_course_module()->id);
  838. $this->update_gradebook(false, $this->get_course_module()->id);
  839. $update = new stdClass();
  840. $update->id = $this->get_instance()->id;
  841. $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
  842. $DB->update_record('assign', $update);
  843. return $result;
  844. }
  845. /**
  846. * Add elements in grading plugin form.
  847. *
  848. * @param mixed $grade stdClass|null
  849. * @param MoodleQuickForm $mform
  850. * @param stdClass $data
  851. * @param int $userid - The userid we are grading
  852. * @return void
  853. */
  854. protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
  855. foreach ($this->feedbackplugins as $plugin) {
  856. if ($plugin->is_enabled() && $plugin->is_visible()) {
  857. $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
  858. $mform->setExpanded('header_' . $plugin->get_type());
  859. if (!$plugin->get_form_elements_for_user($grade, $mform, $data, $userid)) {
  860. $mform->removeElement('header_' . $plugin->get_type());
  861. }
  862. }
  863. }
  864. }
  865. /**
  866. * Add one plugins settings to edit plugin form.
  867. *
  868. * @param assign_plugin $plugin The plugin to add the settings from
  869. * @param MoodleQuickForm $mform The form to add the configuration settings to.
  870. * This form is modified directly (not returned).
  871. * @param array $pluginsenabled A list of form elements to be added to a group.
  872. * The new element is added to this array by this function.
  873. * @return void
  874. */
  875. protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) {
  876. global $CFG;
  877. if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) {
  878. $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
  879. $pluginsenabled[] = $mform->createElement('hidden', $name, 1);
  880. $mform->setType($name, PARAM_BOOL);
  881. $plugin->get_settings($mform);
  882. } else if ($plugin->is_visible() && $plugin->is_configurable()) {
  883. $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
  884. $label = $plugin->get_name();
  885. $label .= ' ' . $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
  886. $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);
  887. $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
  888. if ($plugin->get_config('enabled') !== false) {
  889. $default = $plugin->is_enabled();
  890. }
  891. $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
  892. $plugin->get_settings($mform);
  893. }
  894. }
  895. /**
  896. * Add settings to edit plugin form.
  897. *
  898. * @param MoodleQuickForm $mform The form to add the configuration settings to.
  899. * This form is modified directly (not returned).
  900. * @return void
  901. */
  902. public function add_all_plugin_settings(MoodleQuickForm $mform) {
  903. $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign'));
  904. $submissionpluginsenabled = array();
  905. $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
  906. foreach ($this->submissionplugins as $plugin) {
  907. $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled);
  908. }
  909. $group->setElements($submissionpluginsenabled);
  910. $mform->addElement('header', 'feedbacktypes', get_string('feedbacktypes', 'assign'));
  911. $feedbackpluginsenabled = array();
  912. $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false);
  913. foreach ($this->feedbackplugins as $plugin) {
  914. $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled);
  915. }
  916. $group->setElements($feedbackpluginsenabled);
  917. $mform->setExpanded('submissiontypes');
  918. }
  919. /**
  920. * Allow each plugin an opportunity to update the defaultvalues
  921. * passed in to the settings form (needed to set up draft areas for
  922. * editor and filemanager elements)
  923. *
  924. * @param array $defaultvalues
  925. */
  926. public function plugin_data_preprocessing(&$defaultvalues) {
  927. foreach ($this->submissionplugins as $plugin) {
  928. if ($plugin->is_visible()) {
  929. $plugin->data_preprocessing($defaultvalues);
  930. }
  931. }
  932. foreach ($this->feedbackplugins as $plugin) {
  933. if ($plugin->is_visible()) {
  934. $plugin->data_preprocessing($defaultvalues);
  935. }
  936. }
  937. }
  938. /**
  939. * Get the name of the current module.
  940. *
  941. * @return string the module name (Assignment)
  942. */
  943. protected function get_module_name() {
  944. if (isset(self::$modulename)) {
  945. return self::$modulename;
  946. }
  947. self::$modulename = get_string('modulename', 'assign');
  948. return self::$modulename;
  949. }
  950. /**
  951. * Get the plural name of the current module.
  952. *
  953. * @return string the module name plural (Assignments)
  954. */
  955. protected function get_module_name_plural() {
  956. if (isset(self::$modulenameplural)) {
  957. return self::$modulenameplural;
  958. }
  959. self::$modulenameplural = get_string('modulenameplural', 'assign');
  960. return self::$modulenameplural;
  961. }
  962. /**
  963. * Has this assignment been constructed from an instance?
  964. *
  965. * @return bool
  966. */
  967. public function has_instance() {
  968. return $this->instance || $this->get_course_module();
  969. }
  970. /**
  971. * Get the settings for the current instance of this assignment
  972. *
  973. * @return stdClass The settings
  974. */
  975. public function get_instance() {
  976. global $DB;
  977. if ($this->instance) {
  978. return $this->instance;
  979. }
  980. if ($this->get_course_module()) {
  981. $params = array('id' => $this->get_course_module()->instance);
  982. $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST);
  983. }
  984. if (!$this->instance) {
  985. throw new coding_exception('Improper use of the assignment class. ' .
  986. 'Cannot load the assignment record.');
  987. }
  988. return $this->instance;
  989. }
  990. /**
  991. * Get the primary grade item for this assign instance.
  992. *
  993. * @return stdClass The grade_item record
  994. */
  995. public function get_grade_item() {
  996. if ($this->gradeitem) {
  997. return $this->gradeitem;
  998. }
  999. $instance = $this->get_instance();
  1000. $params = array('itemtype' => 'mod',
  1001. 'itemmodule' => 'assign',
  1002. 'iteminstance' => $instance->id,
  1003. 'courseid' => $instance->course,
  1004. 'itemnumber' => 0);
  1005. $this->gradeitem = grade_item::fetch($params);
  1006. if (!$this->gradeitem) {
  1007. throw new coding_exception('Improper use of the assignment class. ' .
  1008. 'Cannot load the grade item.');
  1009. }
  1010. return $this->gradeitem;
  1011. }
  1012. /**
  1013. * Get the context of the current course.
  1014. *
  1015. * @return mixed context|null The course context
  1016. */
  1017. public function get_course_context() {
  1018. if (!$this->context && !$this->course) {
  1019. throw new coding_exception('Improper use of the assignment class. ' .
  1020. 'Cannot load the course context.');
  1021. }
  1022. if ($this->context) {
  1023. return $this->context->get_course_context();
  1024. } else {
  1025. return context_course::instance($this->course->id);
  1026. }
  1027. }
  1028. /**
  1029. * Get the current course module.
  1030. *
  1031. * @return mixed stdClass|null The course module
  1032. */
  1033. public function get_course_module() {
  1034. if ($this->coursemodule) {
  1035. return $this->coursemodule;
  1036. }
  1037. if (!$this->context) {
  1038. return null;
  1039. }
  1040. if ($this->context->contextlevel == CONTEXT_MODULE) {
  1041. $this->coursemodule = get_coursemodule_from_id('assign',
  1042. $this->context->instanceid,
  1043. 0,
  1044. false,
  1045. MUST_EXIST);
  1046. return $this->coursemodule;
  1047. }
  1048. return null;
  1049. }
  1050. /**
  1051. * Get context module.
  1052. *
  1053. * @return context
  1054. */
  1055. public function get_context() {
  1056. return $this->context;
  1057. }
  1058. /**
  1059. * Get the current course.
  1060. *
  1061. * @return mixed stdClass|null The course
  1062. */
  1063. public function get_course() {
  1064. global $DB;
  1065. if ($this->course) {
  1066. return $this->course;
  1067. }
  1068. if (!$this->context) {
  1069. return null;
  1070. }
  1071. $params = array('id' => $this->get_course_context()->instanceid);
  1072. $this->course = $DB->get_record('course', $params, '*', MUST_EXIST);
  1073. return $this->course;
  1074. }
  1075. /**
  1076. * Return a grade in user-friendly form, whether it's a scale or not.
  1077. *
  1078. * @param mixed $grade int|null
  1079. * @param boolean $editing Are we allowing changes to this grade?
  1080. * @param int $userid The user id the grade belongs to
  1081. * @param int $modified Timestamp from when the grade was last modified
  1082. * @return string User-friendly representation of grade
  1083. */
  1084. public function display_grade($grade, $editing, $userid=0, $modified=0) {
  1085. global $DB;
  1086. static $scalegrades = array();
  1087. $o = '';
  1088. if ($this->get_instance()->grade >= 0) {
  1089. // Normal number.
  1090. if ($editing && $this->get_instance()->grade > 0) {
  1091. if ($grade < 0) {
  1092. $displaygrade = '';
  1093. } else {
  1094. $displaygrade = format_float($grade, 2);
  1095. }
  1096. $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' .
  1097. get_string('usergrade', 'assign') .
  1098. '</label>';
  1099. $o .= '<input type="text"
  1100. id="quickgrade_' . $userid . '"
  1101. name="quickgrade_' . $userid . '"
  1102. value="' . $displaygrade . '"
  1103. size="6"
  1104. maxlength="10"
  1105. class="quickgrade"/>';
  1106. $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
  1107. return $o;
  1108. } else {
  1109. if ($grade == -1 || $grade === null) {
  1110. $o .= '-';
  1111. } else {
  1112. $item = $this->get_grade_item();
  1113. $o .= grade_format_gradevalue($grade, $item);
  1114. if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) {
  1115. // If displaying the raw grade, also display the total value.
  1116. $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
  1117. }
  1118. }
  1119. return $o;
  1120. }
  1121. } else {
  1122. // Scale.
  1123. if (empty($this->cache['scale'])) {
  1124. if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
  1125. $this->cache['scale'] = make_menu_from_list($scale->scale);
  1126. } else {
  1127. $o .= '-';
  1128. return $o;
  1129. }
  1130. }
  1131. if ($editing) {
  1132. $o .= '<label class="accesshide"
  1133. for="quickgrade_' . $userid . '">' .
  1134. get_string('usergrade', 'assign') .
  1135. '</label>';
  1136. $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
  1137. $o .= '<option value="-1">' . get_string('nograde') . '</option>';
  1138. foreach ($this->cache['scale'] as $optionid => $option) {
  1139. $selected = '';
  1140. if ($grade == $optionid) {
  1141. $selected = 'selected="selected"';
  1142. }
  1143. $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
  1144. }
  1145. $o .= '</select>';
  1146. return $o;
  1147. } else {
  1148. $scaleid = (int)$grade;
  1149. if (isset($this->cache['scale'][$scaleid])) {
  1150. $o .= $this->cache['scale'][$scaleid];
  1151. return $o;
  1152. }
  1153. $o .= '-';
  1154. return $o;
  1155. }
  1156. }
  1157. }
  1158. /**
  1159. * Load a list of users enrolled in the current course with the specified permission and group.
  1160. * 0 for no group.
  1161. *
  1162. * @param int $currentgroup
  1163. * @param bool $idsonly
  1164. * @return array List of user records
  1165. */
  1166. public function list_participants($currentgroup, $idsonly) {
  1167. $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
  1168. if (!isset($this->participants[$key])) {
  1169. $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
  1170. $this->show_only_active_users());
  1171. $cm = $this->get_course_module();
  1172. $users = groups_filter_users_by_course_module_visible($cm, $users);
  1173. $this->participants[$key] = $users;
  1174. }
  1175. if ($idsonly) {
  1176. $idslist = array();
  1177. foreach ($this->participants[$key] as $id => $user) {
  1178. $idslist[$id] = new stdClass();
  1179. $idslist[$id]->id = $id;
  1180. }
  1181. return $idslist;
  1182. }
  1183. return $this->participants[$key];
  1184. }
  1185. /**
  1186. * Load a count of valid teams for this assignment.
  1187. *
  1188. * @return int number of valid teams
  1189. */
  1190. public function count_teams() {
  1191. $groups = groups_get_all_groups($this->get_course()->id,
  1192. 0,
  1193. $this->get_instance()->teamsubmissiongroupingid,
  1194. 'g.id');
  1195. $count = count($groups);
  1196. // See if there are any users in the default group.
  1197. $defaultusers = $this->get_submission_group_members(0, true);
  1198. if (count($defaultusers) > 0) {
  1199. $count += 1;
  1200. }
  1201. return $count;
  1202. }
  1203. /**
  1204. * Load a count of active users enrolled in the current course with the specified permission and group.
  1205. * 0 for no group.
  1206. *
  1207. * @param int $currentgroup
  1208. * @return int number of matching users
  1209. */
  1210. public function count_participants($currentgroup) {
  1211. return count($this->list_participants($currentgroup, true));
  1212. }
  1213. /**
  1214. * Load a count of active users submissions in the current module that require grading
  1215. * This means the submission modification time is more recent than the
  1216. * grading modification time and the status is SUBMITTED.
  1217. *
  1218. * @return int number of matching submissions
  1219. */
  1220. public function count_submissions_need_grading() {
  1221. global $DB;
  1222. if ($this->get_instance()->teamsubmission) {
  1223. // This does not make sense for group assignment because the submission is shared.
  1224. return 0;
  1225. }
  1226. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  1227. list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
  1228. $submissionmaxattempt = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt
  1229. FROM {assign_submission} mxs
  1230. WHERE mxs.assignment = :assignid2 GROUP BY mxs.userid';
  1231. $grademaxattempt = 'SELECT mxg.userid, MAX(mxg.attemptnumber) AS maxattempt
  1232. FROM {assign_grades} mxg
  1233. WHERE mxg.assignment = :assignid3 GROUP BY mxg.userid';
  1234. $params['assignid'] = $this->get_instance()->id;
  1235. $params['assignid2'] = $this->get_instance()->id;
  1236. $params['assignid3'] = $this->get_instance()->id;
  1237. $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  1238. $sql = 'SELECT COUNT(s.userid)
  1239. FROM {assign_submission} s
  1240. LEFT JOIN ( ' . $submissionmaxattempt . ' ) smx ON s.userid = smx.userid
  1241. LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON s.userid = gmx.userid
  1242. LEFT JOIN {assign_grades} g ON
  1243. s.assignment = g.assignment AND
  1244. s.userid = g.userid AND
  1245. g.attemptnumber = gmx.maxattempt
  1246. JOIN(' . $esql . ') e ON e.id = s.userid
  1247. WHERE
  1248. s.attemptnumber = smx.maxattempt AND
  1249. s.assignment = :assignid AND
  1250. s.timemodified IS NOT NULL AND
  1251. s.status = :submitted AND
  1252. (s.timemodified > g.timemodified OR g.timemodified IS NULL)';
  1253. return $DB->count_records_sql($sql, $params);
  1254. }
  1255. /**
  1256. * Load a count of grades.
  1257. *
  1258. * @return int number of grades
  1259. */
  1260. public function count_grades() {
  1261. global $DB;
  1262. if (!$this->has_instance()) {
  1263. return 0;
  1264. }
  1265. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  1266. list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
  1267. $params['assignid'] = $this->get_instance()->id;
  1268. $sql = 'SELECT COUNT(g.userid)
  1269. FROM {assign_grades} g
  1270. JOIN(' . $esql . ') e ON e.id = g.userid
  1271. WHERE g.assignment = :assignid';
  1272. return $DB->count_records_sql($sql, $params);
  1273. }
  1274. /**
  1275. * Load a count of submissions.
  1276. *
  1277. * @return int number of submissions
  1278. */
  1279. public function count_submissions() {
  1280. global $DB;
  1281. if (!$this->has_instance()) {
  1282. return 0;
  1283. }
  1284. $params = array();
  1285. if ($this->get_instance()->teamsubmission) {
  1286. // We cannot join on the enrolment tables for group submissions (no userid).
  1287. $sql = 'SELECT COUNT(DISTINCT s.groupid)
  1288. FROM {assign_submission} s
  1289. WHERE
  1290. s.assignment = :assignid AND
  1291. s.timemodified IS NOT NULL AND
  1292. s.userid = :groupuserid';
  1293. $params['assignid'] = $this->get_instance()->id;
  1294. $params['groupuserid'] = 0;
  1295. } else {
  1296. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  1297. list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
  1298. $params['assignid'] = $this->get_instance()->id;
  1299. $sql = 'SELECT COUNT(DISTINCT s.userid)
  1300. FROM {assign_submission} s
  1301. JOIN(' . $esql . ') e ON e.id = s.userid
  1302. WHERE
  1303. s.assignment = :assignid AND
  1304. s.timemodified IS NOT NULL';
  1305. }
  1306. return $DB->count_records_sql($sql, $params);
  1307. }
  1308. /**
  1309. * Load a count of submissions with a specified status.
  1310. *
  1311. * @param string $status The submission status - should match one of the constants
  1312. * @return int number of matching submissions
  1313. */
  1314. public function count_submissions_with_status($status) {
  1315. global $DB;
  1316. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  1317. list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
  1318. $params['assignid'] = $this->get_instance()->id;
  1319. $params['assignid2'] = $this->get_instance()->id;
  1320. $params['submissionstatus'] = $status;
  1321. if ($this->get_instance()->teamsubmission) {
  1322. $maxattemptsql = 'SELECT mxs.groupid, MAX(mxs.attemptnumber) AS maxattempt
  1323. FROM {assign_submission} mxs
  1324. WHERE mxs.assignment = :assignid2 GROUP BY mxs.groupid';
  1325. $sql = 'SELECT COUNT(s.groupid)
  1326. FROM {assign_submission} s
  1327. JOIN(' . $maxattemptsql . ') smx ON s.groupid = smx.groupid
  1328. WHERE
  1329. s.attemptnumber = smx.maxattempt AND
  1330. s.assignment = :assignid AND
  1331. s.timemodified IS NOT NULL AND
  1332. s.userid = :groupuserid AND
  1333. s.status = :submissionstatus';
  1334. $params['groupuserid'] = 0;
  1335. } else {
  1336. $maxattemptsql = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt
  1337. FROM {assign_submission} mxs
  1338. WHERE mxs.assignment = :assignid2 GROUP BY mxs.userid';
  1339. $sql = 'SELECT COUNT(s.userid)
  1340. FROM {assign_submission} s
  1341. JOIN(' . $esql . ') e ON e.id = s.userid
  1342. JOIN(' . $maxattemptsql . ') smx ON s.userid = smx.userid
  1343. WHERE
  1344. s.attemptnumber = smx.maxattempt AND
  1345. s.assignment = :assignid AND
  1346. s.timemodified IS NOT NULL AND
  1347. s.status = :submissionstatus';
  1348. }
  1349. return $DB->count_records_sql($sql, $params);
  1350. }
  1351. /**
  1352. * Utility function to get the userid for every row in the grading table
  1353. * so the order can be frozen while we iterate it.
  1354. *
  1355. * @return array An array of userids
  1356. */
  1357. protected function get_grading_userid_list() {
  1358. $filter = get_user_preferences('assign_filter', '');
  1359. $table = new assign_grading_table($this, 0, $filter, 0, false);
  1360. $useridlist = $table->get_column_data('userid');
  1361. return $useridlist;
  1362. }
  1363. /**
  1364. * Generate zip file from array of given files.
  1365. *
  1366. * @param array $filesforzipping - array of files to pass into archive_to_pathname.
  1367. * This array is indexed by the final file name and each
  1368. * element in the array is an instance of a stored_file object.
  1369. * @return path of temp file - note this returned file does
  1370. * not have a .zip extension - it is a temp file.
  1371. */
  1372. protected function pack_files($filesforzipping) {
  1373. global $CFG;
  1374. // Create path for new zip file.
  1375. $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
  1376. // Zip files.
  1377. $zipper = new zip_packer();
  1378. if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
  1379. return $tempzip;
  1380. }
  1381. return false;
  1382. }
  1383. /**
  1384. * Finds all assignment notifications that have yet to be mailed out, and mails them.
  1385. *
  1386. * Cron function to be run periodically according to the moodle cron.
  1387. *
  1388. * @return bool
  1389. */
  1390. public static function cron() {
  1391. global $DB;
  1392. // Only ever send a max of one days worth of updates.
  1393. $yesterday = time() - (24 * 3600);
  1394. $timenow = time();
  1395. $lastcron = $DB->get_field('modules', 'lastcron', array('name'=>'mod_assign'));
  1396. // Collect all submissions from the past 24 hours that require mailing.
  1397. // Submissions are excluded if the assignment is hidden in the gradebook.
  1398. $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,
  1399. g.*, g.timemodified as lastmodified, cm.id as cmid
  1400. FROM {assign} a
  1401. JOIN {assign_grades} g ON g.assignment = a.id
  1402. LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid
  1403. JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id
  1404. JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'
  1405. JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name
  1406. WHERE g.timemodified >= :yesterday AND
  1407. g.timemodified <= :today AND
  1408. uf.mailed = 0 AND gri.hidden = 0
  1409. ORDER BY a.course, cm.id";
  1410. $params = array('yesterday' => $yesterday, 'today' => $timenow);
  1411. $submissions = $DB->get_records_sql($sql, $params);
  1412. if (!empty($submissions)) {
  1413. mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
  1414. // Preload courses we are going to need those.
  1415. $courseids = array();
  1416. foreach ($submissions as $submission) {
  1417. $courseids[] = $submission->course;
  1418. }
  1419. // Filter out duplicates.
  1420. $courseids = array_unique($courseids);
  1421. $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
  1422. list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
  1423. $sql = 'SELECT c.*, ' . $ctxselect .
  1424. ' FROM {course} c
  1425. LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
  1426. WHERE c.id ' . $courseidsql;
  1427. $params['contextlevel'] = CONTEXT_COURSE;
  1428. $courses = $DB->get_records_sql($sql, $params);
  1429. // Clean up... this could go on for a while.
  1430. unset($courseids);
  1431. unset($ctxselect);
  1432. unset($courseidsql);
  1433. unset($params);
  1434. // Message students about new feedback.
  1435. foreach ($submissions as $submission) {
  1436. mtrace("Processing assignment submission $submission->id ...");
  1437. // Do not cache user lookups - could be too many.
  1438. if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
  1439. mtrace('Could not find user ' . $submission->userid);
  1440. continue;
  1441. }
  1442. // Use a cache to prevent the same DB queries happening over and over.
  1443. if (!array_key_exists($submission->course, $courses)) {
  1444. mtrace('Could not find course ' . $submission->course);
  1445. continue;
  1446. }
  1447. $course = $courses[$submission->course];
  1448. if (isset($course->ctxid)) {
  1449. // Context has not yet been preloaded. Do so now.
  1450. context_helper::preload_from_record($course);
  1451. }
  1452. // Override the language and timezone of the "current" user, so that
  1453. // mail is customised for the receiver.
  1454. cron_setup_user($user, $course);
  1455. // Context lookups are already cached.
  1456. $coursecontext = context_course::instance($course->id);
  1457. if (!is_enrolled($coursecontext, $user->id)) {
  1458. $courseshortname = format_string($course->shortname,
  1459. true,
  1460. array('context' => $coursecontext));
  1461. mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
  1462. continue;
  1463. }
  1464. if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
  1465. mtrace('Could not find grader ' . $submission->grader);
  1466. continue;
  1467. }
  1468. $modinfo = get_fast_modinfo($course, $user->id);
  1469. $cm = $modinfo->get_cm($submission->cmid);
  1470. // Context lookups are already cached.
  1471. $contextmodule = context_module::instance($cm->id);
  1472. if (!$cm->uservisible) {
  1473. // Hold mail notification for assignments the user cannot access until later.
  1474. continue;
  1475. }
  1476. // Need to send this to the student.
  1477. $messagetype = 'feedbackavailable';
  1478. $eventtype = 'assign_notification';
  1479. $updatetime = $submission->lastmodified;
  1480. $modulename = get_string('modulename', 'assign');
  1481. $uniqueid = 0;
  1482. if ($submission->blindmarking && !$submission->revealidentities) {
  1483. $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
  1484. }
  1485. $showusers = $submission->blindmarking && !$submission->revealidentities;
  1486. self::send_assignment_notification($grader,
  1487. $user,
  1488. $messagetype,
  1489. $eventtype,
  1490. $updatetime,
  1491. $cm,
  1492. $contextmodule,
  1493. $course,
  1494. $modulename,
  1495. $submission->name,
  1496. $showusers,
  1497. $uniqueid);
  1498. $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment));
  1499. if ($flags) {
  1500. $flags->mailed = 1;
  1501. $DB->update_record('assign_user_flags', $flags);
  1502. } else {
  1503. $flags = new stdClass();
  1504. $flags->userid = $user->id;
  1505. $flags->assignment = $submission->assignment;
  1506. $flags->mailed = 1;
  1507. $DB->insert_record('assign_user_flags', $flags);
  1508. }
  1509. mtrace('Done');
  1510. }
  1511. mtrace('Done processing ' . count($submissions) . ' assignment submissions');
  1512. cron_setup_user();
  1513. // Free up memory just to be sure.
  1514. unset($courses);
  1515. }
  1516. // Update calendar events to provide a description.
  1517. $sql = 'SELECT id
  1518. FROM {assign}
  1519. WHERE
  1520. allowsubmissionsfromdate >= :lastcron AND
  1521. allowsubmissionsfromdate <= :timenow AND
  1522. alwaysshowdescription = 0';
  1523. $params = array('lastcron' => $lastcron, 'timenow' => $timenow);
  1524. $newlyavailable = $DB->get_records_sql($sql, $params);
  1525. foreach ($newlyavailable as $record) {
  1526. $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST);
  1527. $context = context_module::instance($cm->id);
  1528. $assignment = new assign($context, null, null);
  1529. $assignment->update_calendar($cm->id);
  1530. }
  1531. return true;
  1532. }
  1533. /**
  1534. * Mark in the database that this grade record should have an update notification sent by cron.
  1535. *
  1536. * @param stdClass $grade a grade record keyed on id
  1537. * @return bool true for success
  1538. */
  1539. public function notify_grade_modified($grade) {
  1540. global $DB;
  1541. $flags = $this->get_user_flags($grade->userid, true);
  1542. if ($flags->mailed != 1) {
  1543. $flags->mailed = 0;
  1544. }
  1545. return $this->update_user_flags($flags);
  1546. }
  1547. /**
  1548. * Update user flags for this user in this assignment.
  1549. *
  1550. * @param stdClass $flags a flags record keyed on id
  1551. * @return bool true for success
  1552. */
  1553. public function update_user_flags($flags) {
  1554. global $DB;
  1555. if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) {
  1556. return false;
  1557. }
  1558. $result = $DB->update_record('assign_user_flags', $flags);
  1559. return $result;
  1560. }
  1561. /**
  1562. * Update a grade in the grade table for the assignment and in the gradebook.
  1563. *
  1564. * @param stdClass $grade a grade record keyed on id
  1565. * @return bool true for success
  1566. */
  1567. public function update_grade($grade) {
  1568. global $DB;
  1569. $grade->timemodified = time();
  1570. if (!empty($grade->workflowstate)) {
  1571. $validstates = $this->get_marking_workflow_states_for_current_user();
  1572. if (!array_key_exists($grade->workflowstate, $validstates)) {
  1573. return false;
  1574. }
  1575. }
  1576. if ($grade->grade && $grade->grade != -1) {
  1577. if ($this->get_instance()->grade > 0) {
  1578. if (!is_numeric($grade->grade)) {
  1579. return false;
  1580. } else if ($grade->grade > $this->get_instance()->grade) {
  1581. return false;
  1582. } else if ($grade->grade < 0) {
  1583. return false;
  1584. }
  1585. } else {
  1586. // This is a scale.
  1587. if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
  1588. $scaleoptions = make_menu_from_list($scale->scale);
  1589. if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
  1590. return false;
  1591. }
  1592. }
  1593. }
  1594. }
  1595. if (empty($grade->attemptnumber)) {
  1596. // Set it to the default.
  1597. $grade->attemptnumber = 0;
  1598. }
  1599. $result = $DB->update_record('assign_grades', $grade);
  1600. // Only push to gradebook if the update is for the latest attempt.
  1601. $submission = null;
  1602. if ($this->get_instance()->teamsubmission) {
  1603. $submission = $this->get_group_submission($grade->userid, 0, false);
  1604. } else {
  1605. $submission = $this->get_user_submission($grade->userid, false);
  1606. }
  1607. // Not the latest attempt.
  1608. if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
  1609. return true;
  1610. }
  1611. if ($result) {
  1612. $this->gradebook_item_update(null, $grade);
  1613. }
  1614. return $result;
  1615. }
  1616. /**
  1617. * View the grant extension date page.
  1618. *
  1619. * Uses url parameters 'userid'
  1620. * or from parameter 'selectedusers'
  1621. *
  1622. * @param moodleform $mform - Used for validation of the submitted data
  1623. * @return string
  1624. */
  1625. protected function view_grant_extension($mform) {
  1626. global $DB, $CFG;
  1627. require_once($CFG->dirroot . '/mod/assign/extensionform.php');
  1628. $o = '';
  1629. $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
  1630. $data = new stdClass();
  1631. $data->extensionduedate = null;
  1632. $userid = 0;
  1633. if (!$batchusers) {
  1634. $userid = required_param('userid', PARAM_INT);
  1635. $flags = $this->get_user_flags($userid, false);
  1636. $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
  1637. if ($flags) {
  1638. $data->extensionduedate = $flags->extensionduedate;
  1639. }
  1640. $data->userid = $userid;
  1641. } else {
  1642. $data->batchusers = $batchusers;
  1643. }
  1644. $header = new assign_header($this->get_instance(),
  1645. $this->get_context(),
  1646. $this->show_intro(),
  1647. $this->get_course_module()->id,
  1648. get_string('grantextension', 'assign'));
  1649. $o .= $this->get_renderer()->render($header);
  1650. if (!$mform) {
  1651. $formparams = array($this->get_course_module()->id,
  1652. $userid,
  1653. $batchusers,
  1654. $this->get_instance(),
  1655. $data);
  1656. $mform = new mod_assign_extension_form(null, $formparams);
  1657. }
  1658. $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
  1659. $o .= $this->view_footer();
  1660. return $o;
  1661. }
  1662. /**
  1663. * Get a list of the users in the same group as this user.
  1664. *
  1665. * @param int $groupid The id of the group whose members we want or 0 for the default group
  1666. * @param bool $onlyids Whether to retrieve only the user id's
  1667. * @return array The users (possibly id's only)
  1668. */
  1669. public function get_submission_group_members($groupid, $onlyids) {
  1670. $members = array();
  1671. if ($groupid != 0) {
  1672. if ($onlyids) {
  1673. $allusers = groups_get_members($groupid, 'u.id');
  1674. } else {
  1675. $allusers = groups_get_members($groupid);
  1676. }
  1677. foreach ($allusers as $user) {
  1678. if ($this->get_submission_group($user->id)) {
  1679. $members[] = $user;
  1680. }
  1681. }
  1682. } else {
  1683. $allusers = $this->list_participants(null, $onlyids);
  1684. foreach ($allusers as $user) {
  1685. if ($this->get_submission_group($user->id) == null) {
  1686. $members[] = $user;
  1687. }
  1688. }
  1689. }
  1690. // Exclude suspended users, if user can't see them.
  1691. if (!has_capability('moodle/course:viewsuspendedusers', $this->context)) {
  1692. foreach ($members as $key => $member) {
  1693. if (!$this->is_active_user($member->id)) {
  1694. unset($members[$key]);
  1695. }
  1696. }
  1697. }
  1698. return $members;
  1699. }
  1700. /**
  1701. * Get a list of the users in the same group as this user that have not submitted the assignment.
  1702. *
  1703. * @param int $groupid The id of the group whose members we want or 0 for the default group
  1704. * @param bool $onlyids Whether to retrieve only the user id's
  1705. * @return array The users (possibly id's only)
  1706. */
  1707. public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
  1708. $instance = $this->get_instance();
  1709. if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
  1710. return array();
  1711. }
  1712. $members = $this->get_submission_group_members($groupid, $onlyids);
  1713. foreach ($members as $id => $member) {
  1714. $submission = $this->get_user_submission($member->id, false);
  1715. if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  1716. unset($members[$id]);
  1717. } else {
  1718. if ($this->is_blind_marking()) {
  1719. $members[$id]->alias = get_string('hiddenuser', 'assign') .
  1720. $this->get_uniqueid_for_user($id);
  1721. }
  1722. }
  1723. }
  1724. return $members;
  1725. }
  1726. /**
  1727. * Load the group submission object for a particular user, optionally creating it if required.
  1728. *
  1729. * @param int $userid The id of the user whose submission we want
  1730. * @param int $groupid The id of the group for this user - may be 0 in which
  1731. * case it is determined from the userid.
  1732. * @param bool $create If set to true a new submission object will be created in the database
  1733. * @param int $attemptnumber - -1 means the latest attempt
  1734. * @return stdClass The submission
  1735. */
  1736. public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
  1737. global $DB;
  1738. if ($groupid == 0) {
  1739. $group = $this->get_submission_group($userid);
  1740. if ($group) {
  1741. $groupid = $group->id;
  1742. }
  1743. }
  1744. // Now get the group submission.
  1745. $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
  1746. if ($attemptnumber >= 0) {
  1747. $params['attemptnumber'] = $attemptnumber;
  1748. }
  1749. // Only return the row with the highest attemptnumber.
  1750. $submission = null;
  1751. $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
  1752. if ($submissions) {
  1753. $submission = reset($submissions);
  1754. }
  1755. if ($submission) {
  1756. return $submission;
  1757. }
  1758. if ($create) {
  1759. $submission = new stdClass();
  1760. $submission->assignment = $this->get_instance()->id;
  1761. $submission->userid = 0;
  1762. $submission->groupid = $groupid;
  1763. $submission->timecreated = time();
  1764. $submission->timemodified = $submission->timecreated;
  1765. if ($attemptnumber >= 0) {
  1766. $submission->attemptnumber = $attemptnumber;
  1767. } else {
  1768. $submission->attemptnumber = 0;
  1769. }
  1770. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  1771. $sid = $DB->insert_record('assign_submission', $submission);
  1772. $submission->id = $sid;
  1773. return $submission;
  1774. }
  1775. return false;
  1776. }
  1777. /**
  1778. * View a summary listing of all assignments in the current course.
  1779. *
  1780. * @return string
  1781. */
  1782. private function view_course_index() {
  1783. global $USER;
  1784. $o = '';
  1785. $course = $this->get_course();
  1786. $strplural = get_string('modulenameplural', 'assign');
  1787. if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
  1788. $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
  1789. $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
  1790. return $o;
  1791. }
  1792. $strsectionname = '';
  1793. $usesections = course_format_uses_sections($course->format);
  1794. $modinfo = get_fast_modinfo($course);
  1795. if ($usesections) {
  1796. $strsectionname = get_string('sectionname', 'format_'.$course->format);
  1797. $sections = $modinfo->get_section_info_all();
  1798. }
  1799. $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
  1800. $timenow = time();
  1801. $currentsection = '';
  1802. foreach ($modinfo->instances['assign'] as $cm) {
  1803. if (!$cm->uservisible) {
  1804. continue;
  1805. }
  1806. $timedue = $cms[$cm->id]->duedate;
  1807. $sectionname = '';
  1808. if ($usesections && $cm->sectionnum) {
  1809. $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
  1810. }
  1811. $submitted = '';
  1812. $context = context_module::instance($cm->id);
  1813. $assignment = new assign($context, $cm, $course);
  1814. if (has_capability('mod/assign:grade', $context)) {
  1815. $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
  1816. } else if (has_capability('mod/assign:submit', $context)) {
  1817. $usersubmission = $assignment->get_user_submission($USER->id, false);
  1818. if (!empty($usersubmission->status)) {
  1819. $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
  1820. } else {
  1821. $submitted = get_string('submissionstatus_', 'assign');
  1822. }
  1823. }
  1824. $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
  1825. if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
  1826. !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
  1827. $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
  1828. } else {
  1829. $grade = '-';
  1830. }
  1831. $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
  1832. }
  1833. $o .= $this->get_renderer()->render($courseindexsummary);
  1834. $o .= $this->view_footer();
  1835. return $o;
  1836. }
  1837. /**
  1838. * View a page rendered by a plugin.
  1839. *
  1840. * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
  1841. *
  1842. * @return string
  1843. */
  1844. protected function view_plugin_page() {
  1845. global $USER;
  1846. $o = '';
  1847. $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
  1848. $plugintype = required_param('plugin', PARAM_TEXT);
  1849. $pluginaction = required_param('pluginaction', PARAM_ALPHA);
  1850. $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
  1851. if (!$plugin) {
  1852. print_error('invalidformdata', '');
  1853. return;
  1854. }
  1855. $o .= $plugin->view_page($pluginaction);
  1856. return $o;
  1857. }
  1858. /**
  1859. * This is used for team assignments to get the group for the specified user.
  1860. * If the user is a member of multiple or no groups this will return false
  1861. *
  1862. * @param int $userid The id of the user whose submission we want
  1863. * @return mixed The group or false
  1864. */
  1865. public function get_submission_group($userid) {
  1866. $grouping = $this->get_instance()->teamsubmissiongroupingid;
  1867. $groups = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
  1868. if (count($groups) != 1) {
  1869. return false;
  1870. }
  1871. return array_pop($groups);
  1872. }
  1873. /**
  1874. * Display the submission that is used by a plugin.
  1875. *
  1876. * Uses url parameters 'sid', 'gid' and 'plugin'.
  1877. *
  1878. * @param string $pluginsubtype
  1879. * @return string
  1880. */
  1881. protected function view_plugin_content($pluginsubtype) {
  1882. global $USER;
  1883. $o = '';
  1884. $submissionid = optional_param('sid', 0, PARAM_INT);
  1885. $gradeid = optional_param('gid', 0, PARAM_INT);
  1886. $plugintype = required_param('plugin', PARAM_TEXT);
  1887. $item = null;
  1888. if ($pluginsubtype == 'assignsubmission') {
  1889. $plugin = $this->get_submission_plugin_by_type($plugintype);
  1890. if ($submissionid <= 0) {
  1891. throw new coding_exception('Submission id should not be 0');
  1892. }
  1893. $item = $this->get_submission($submissionid);
  1894. // Check permissions.
  1895. if ($item->userid != $USER->id) {
  1896. require_capability('mod/assign:grade', $this->context);
  1897. }
  1898. $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
  1899. $this->get_context(),
  1900. $this->show_intro(),
  1901. $this->get_course_module()->id,
  1902. $plugin->get_name()));
  1903. $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
  1904. $item,
  1905. assign_submission_plugin_submission::FULL,
  1906. $this->get_course_module()->id,
  1907. $this->get_return_action(),
  1908. $this->get_return_params()));
  1909. $logmessage = get_string('viewsubmissionforuser', 'assign', $item->userid);
  1910. $this->add_to_log('view submission', $logmessage);
  1911. } else {
  1912. $plugin = $this->get_feedback_plugin_by_type($plugintype);
  1913. if ($gradeid <= 0) {
  1914. throw new coding_exception('Grade id should not be 0');
  1915. }
  1916. $item = $this->get_grade($gradeid);
  1917. // Check permissions.
  1918. if ($item->userid != $USER->id) {
  1919. require_capability('mod/assign:grade', $this->context);
  1920. }
  1921. $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
  1922. $this->get_context(),
  1923. $this->show_intro(),
  1924. $this->get_course_module()->id,
  1925. $plugin->get_name()));
  1926. $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
  1927. $item,
  1928. assign_feedback_plugin_feedback::FULL,
  1929. $this->get_course_module()->id,
  1930. $this->get_return_action(),
  1931. $this->get_return_params()));
  1932. $logmessage = get_string('viewfeedbackforuser', 'assign', $item->userid);
  1933. $this->add_to_log('view feedback', $logmessage);
  1934. }
  1935. $o .= $this->view_return_links();
  1936. $o .= $this->view_footer();
  1937. return $o;
  1938. }
  1939. /**
  1940. * Rewrite plugin file urls so they resolve correctly in an exported zip.
  1941. *
  1942. * @param string $text - The replacement text
  1943. * @param stdClass $user - The user record
  1944. * @param assign_plugin $plugin - The assignment plugin
  1945. */
  1946. public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
  1947. $groupmode = groups_get_activity_groupmode($this->get_course_module());
  1948. $groupname = '';
  1949. if ($groupmode) {
  1950. $groupid = groups_get_activity_group($this->get_course_module(), true);
  1951. $groupname = groups_get_group_name($groupid).'-';
  1952. }
  1953. if ($this->is_blind_marking()) {
  1954. $prefix = $groupname . get_string('participant', 'assign');
  1955. $prefix = str_replace('_', ' ', $prefix);
  1956. $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
  1957. } else {
  1958. $prefix = $groupname . fullname($user);
  1959. $prefix = str_replace('_', ' ', $prefix);
  1960. $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
  1961. }
  1962. $subtype = $plugin->get_subtype();
  1963. $type = $plugin->get_type();
  1964. $prefix = $prefix . $subtype . '_' . $type . '_';
  1965. $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
  1966. return $result;
  1967. }
  1968. /**
  1969. * Render the content in editor that is often used by plugin.
  1970. *
  1971. * @param string $filearea
  1972. * @param int $submissionid
  1973. * @param string $plugintype
  1974. * @param string $editor
  1975. * @param string $component
  1976. * @return string
  1977. */
  1978. public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
  1979. global $CFG;
  1980. $result = '';
  1981. $plugin = $this->get_submission_plugin_by_type($plugintype);
  1982. $text = $plugin->get_editor_text($editor, $submissionid);
  1983. $format = $plugin->get_editor_format($editor, $submissionid);
  1984. $finaltext = file_rewrite_pluginfile_urls($text,
  1985. 'pluginfile.php',
  1986. $this->get_context()->id,
  1987. $component,
  1988. $filearea,
  1989. $submissionid);
  1990. $params = array('overflowdiv' => true, 'context' => $this->get_context());
  1991. $result .= format_text($finaltext, $format, $params);
  1992. if ($CFG->enableportfolios) {
  1993. require_once($CFG->libdir . '/portfoliolib.php');
  1994. $button = new portfolio_add_button();
  1995. $portfolioparams = array('cmid' => $this->get_course_module()->id,
  1996. 'sid' => $submissionid,
  1997. 'plugin' => $plugintype,
  1998. 'editor' => $editor,
  1999. 'area'=>$filearea);
  2000. $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
  2001. $fs = get_file_storage();
  2002. if ($files = $fs->get_area_files($this->context->id,
  2003. $component,
  2004. $filearea,
  2005. $submissionid,
  2006. 'timemodified',
  2007. false)) {
  2008. $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
  2009. } else {
  2010. $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
  2011. }
  2012. $result .= $button->to_html();
  2013. }
  2014. return $result;
  2015. }
  2016. /**
  2017. * Display a continue page.
  2018. *
  2019. * @param string $message - The message to display
  2020. * @return string
  2021. */
  2022. protected function view_savegrading_result($message) {
  2023. $o = '';
  2024. $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
  2025. $this->get_context(),
  2026. $this->show_intro(),
  2027. $this->get_course_module()->id,
  2028. get_string('savegradingresult', 'assign')));
  2029. $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
  2030. $message,
  2031. $this->get_course_module()->id);
  2032. $o .= $this->get_renderer()->render($gradingresult);
  2033. $o .= $this->view_footer();
  2034. return $o;
  2035. }
  2036. /**
  2037. * Display a grading error.
  2038. *
  2039. * @param string $message - The description of the result
  2040. * @return string
  2041. */
  2042. protected function view_quickgrading_result($message) {
  2043. $o = '';
  2044. $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
  2045. $this->get_context(),
  2046. $this->show_intro(),
  2047. $this->get_course_module()->id,
  2048. get_string('quickgradingresult', 'assign')));
  2049. $lastpage = optional_param('lastpage', null, PARAM_INT);
  2050. $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
  2051. $message,
  2052. $this->get_course_module()->id,
  2053. false,
  2054. $lastpage);
  2055. $o .= $this->get_renderer()->render($gradingresult);
  2056. $o .= $this->view_footer();
  2057. return $o;
  2058. }
  2059. /**
  2060. * Display the page footer.
  2061. *
  2062. * @return string
  2063. */
  2064. protected function view_footer() {
  2065. return $this->get_renderer()->render_footer();
  2066. }
  2067. /**
  2068. * Does this user have grade permission for this assignment?
  2069. *
  2070. * @return bool
  2071. */
  2072. public function can_grade() {
  2073. // Permissions check.
  2074. if (!has_capability('mod/assign:grade', $this->context)) {
  2075. return false;
  2076. }
  2077. return true;
  2078. }
  2079. /**
  2080. * Download a zip file of all assignment submissions.
  2081. *
  2082. * @return string - If an error occurs, this will contain the error page.
  2083. */
  2084. protected function download_submissions() {
  2085. global $CFG, $DB;
  2086. // More efficient to load this here.
  2087. require_once($CFG->libdir.'/filelib.php');
  2088. require_capability('mod/assign:grade', $this->context);
  2089. // Load all users with submit.
  2090. $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
  2091. $this->show_only_active_users());
  2092. // Build a list of files to zip.
  2093. $filesforzipping = array();
  2094. $fs = get_file_storage();
  2095. $groupmode = groups_get_activity_groupmode($this->get_course_module());
  2096. // All users.
  2097. $groupid = 0;
  2098. $groupname = '';
  2099. if ($groupmode) {
  2100. $groupid = groups_get_activity_group($this->get_course_module(), true);
  2101. $groupname = groups_get_group_name($groupid).'-';
  2102. }
  2103. // Construct the zip file name.
  2104. $filename = clean_filename($this->get_course()->shortname . '-' .
  2105. $this->get_instance()->name . '-' .
  2106. $groupname.$this->get_course_module()->id . '.zip');
  2107. // Get all the files for each student.
  2108. foreach ($students as $student) {
  2109. $userid = $student->id;
  2110. if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
  2111. // Get the plugins to add their own files to the zip.
  2112. $submissiongroup = false;
  2113. $groupname = '';
  2114. if ($this->get_instance()->teamsubmission) {
  2115. $submission = $this->get_group_submission($userid, 0, false);
  2116. $submissiongroup = $this->get_submission_group($userid);
  2117. if ($submissiongroup) {
  2118. $groupname = $submissiongroup->name . '-';
  2119. } else {
  2120. $groupname = get_string('defaultteam', 'assign') . '-';
  2121. }
  2122. } else {
  2123. $submission = $this->get_user_submission($userid, false);
  2124. }
  2125. if ($this->is_blind_marking()) {
  2126. $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
  2127. $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
  2128. } else {
  2129. $prefix = str_replace('_', ' ', $groupname . fullname($student));
  2130. $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
  2131. }
  2132. if ($submission) {
  2133. foreach ($this->submissionplugins as $plugin) {
  2134. if ($plugin->is_enabled() && $plugin->is_visible()) {
  2135. $pluginfiles = $plugin->get_files($submission, $student);
  2136. foreach ($pluginfiles as $zipfilename => $file) {
  2137. $subtype = $plugin->get_subtype();
  2138. $type = $plugin->get_type();
  2139. $prefixedfilename = clean_filename($prefix .
  2140. $subtype .
  2141. '_' .
  2142. $type .
  2143. '_' .
  2144. $zipfilename);
  2145. $filesforzipping[$prefixedfilename] = $file;
  2146. }
  2147. }
  2148. }
  2149. }
  2150. }
  2151. }
  2152. $result = '';
  2153. if (count($filesforzipping) == 0) {
  2154. $header = new assign_header($this->get_instance(),
  2155. $this->get_context(),
  2156. '',
  2157. $this->get_course_module()->id,
  2158. get_string('downloadall', 'assign'));
  2159. $result .= $this->get_renderer()->render($header);
  2160. $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
  2161. $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
  2162. 'action'=>'grading'));
  2163. $result .= $this->get_renderer()->continue_button($url);
  2164. $result .= $this->view_footer();
  2165. } else if ($zipfile = $this->pack_files($filesforzipping)) {
  2166. $addtolog = $this->add_to_log('download all submissions', get_string('downloadall', 'assign'), '', true);
  2167. $params = array(
  2168. 'context' => $this->context,
  2169. 'objectid' => $this->get_instance()->id
  2170. );
  2171. $event = \mod_assign\event\all_submissions_downloaded::create($params);
  2172. $event->set_legacy_logdata($addtolog);
  2173. $event->trigger();
  2174. // Send file and delete after sending.
  2175. send_temp_file($zipfile, $filename);
  2176. // We will not get here - send_temp_file calls exit.
  2177. }
  2178. return $result;
  2179. }
  2180. /**
  2181. * Util function to add a message to the log.
  2182. *
  2183. * @param string $action The current action
  2184. * @param string $info A detailed description of the change. But no more than 255 characters.
  2185. * @param string $url The url to the assign module instance.
  2186. * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
  2187. * retrieve the arguments to use them with the new event system (Event 2).
  2188. * @return void|array
  2189. */
  2190. public function add_to_log($action = '', $info = '', $url='', $return = false) {
  2191. global $USER;
  2192. $fullurl = 'view.php?id=' . $this->get_course_module()->id;
  2193. if ($url != '') {
  2194. $fullurl .= '&' . $url;
  2195. }
  2196. $args = array(
  2197. $this->get_course()->id,
  2198. 'assign',
  2199. $action,
  2200. $fullurl,
  2201. $info,
  2202. $this->get_course_module()->id
  2203. );
  2204. if ($return) {
  2205. return $args;
  2206. }
  2207. call_user_func_array('add_to_log', $args);
  2208. }
  2209. /**
  2210. * Lazy load the page renderer and expose the renderer to plugins.
  2211. *
  2212. * @return assign_renderer
  2213. */
  2214. public function get_renderer() {
  2215. global $PAGE;
  2216. if ($this->output) {
  2217. return $this->output;
  2218. }
  2219. $this->output = $PAGE->get_renderer('mod_assign');
  2220. return $this->output;
  2221. }
  2222. /**
  2223. * Load the submission object for a particular user, optionally creating it if required.
  2224. *
  2225. * For team assignments there are 2 submissions - the student submission and the team submission
  2226. * All files are associated with the team submission but the status of the students contribution is
  2227. * recorded separately.
  2228. *
  2229. * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
  2230. * @param bool $create optional - defaults to false. If set to true a new submission object
  2231. * will be created in the database.
  2232. * @param int $attemptnumber - -1 means the latest attempt
  2233. * @return stdClass The submission
  2234. */
  2235. public function get_user_submission($userid, $create, $attemptnumber=-1) {
  2236. global $DB, $USER;
  2237. if (!$userid) {
  2238. $userid = $USER->id;
  2239. }
  2240. // If the userid is not null then use userid.
  2241. $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
  2242. if ($attemptnumber >= 0) {
  2243. $params['attemptnumber'] = $attemptnumber;
  2244. }
  2245. // Only return the row with the highest attemptnumber.
  2246. $submission = null;
  2247. $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
  2248. if ($submissions) {
  2249. $submission = reset($submissions);
  2250. }
  2251. if ($submission) {
  2252. return $submission;
  2253. }
  2254. if ($create) {
  2255. $submission = new stdClass();
  2256. $submission->assignment = $this->get_instance()->id;
  2257. $submission->userid = $userid;
  2258. $submission->timecreated = time();
  2259. $submission->timemodified = $submission->timecreated;
  2260. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  2261. if ($attemptnumber >= 0) {
  2262. $submission->attemptnumber = $attemptnumber;
  2263. } else {
  2264. $submission->attemptnumber = 0;
  2265. }
  2266. $sid = $DB->insert_record('assign_submission', $submission);
  2267. $submission->id = $sid;
  2268. return $submission;
  2269. }
  2270. return false;
  2271. }
  2272. /**
  2273. * Load the submission object from it's id.
  2274. *
  2275. * @param int $submissionid The id of the submission we want
  2276. * @return stdClass The submission
  2277. */
  2278. protected function get_submission($submissionid) {
  2279. global $DB;
  2280. $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
  2281. return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
  2282. }
  2283. /**
  2284. * This will retrieve a user flags object from the db optionally creating it if required.
  2285. * The user flags was split from the user_grades table in 2.5.
  2286. *
  2287. * @param int $userid The user we are getting the flags for.
  2288. * @param bool $create If true the flags record will be created if it does not exist
  2289. * @return stdClass The flags record
  2290. */
  2291. public function get_user_flags($userid, $create) {
  2292. global $DB, $USER;
  2293. // If the userid is not null then use userid.
  2294. if (!$userid) {
  2295. $userid = $USER->id;
  2296. }
  2297. $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
  2298. $flags = $DB->get_record('assign_user_flags', $params);
  2299. if ($flags) {
  2300. return $flags;
  2301. }
  2302. if ($create) {
  2303. $flags = new stdClass();
  2304. $flags->assignment = $this->get_instance()->id;
  2305. $flags->userid = $userid;
  2306. $flags->locked = 0;
  2307. $flags->extensionduedate = 0;
  2308. $flags->workflowstate = '';
  2309. $flags->allocatedmarker = 0;
  2310. // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
  2311. // This is because students only want to be notified about certain types of update (grades and feedback).
  2312. $flags->mailed = 2;
  2313. $fid = $DB->insert_record('assign_user_flags', $flags);
  2314. $flags->id = $fid;
  2315. return $flags;
  2316. }
  2317. return false;
  2318. }
  2319. /**
  2320. * This will retrieve a grade object from the db, optionally creating it if required.
  2321. *
  2322. * @param int $userid The user we are grading
  2323. * @param bool $create If true the grade will be created if it does not exist
  2324. * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
  2325. * @return stdClass The grade record
  2326. */
  2327. public function get_user_grade($userid, $create, $attemptnumber=-1) {
  2328. global $DB, $USER;
  2329. // If the userid is not null then use userid.
  2330. if (!$userid) {
  2331. $userid = $USER->id;
  2332. }
  2333. $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
  2334. if ($attemptnumber < 0) {
  2335. // Make sure this grade matches the latest submission attempt.
  2336. if ($this->get_instance()->teamsubmission) {
  2337. $submission = $this->get_group_submission($userid, 0, false);
  2338. } else {
  2339. $submission = $this->get_user_submission($userid, false);
  2340. }
  2341. if ($submission) {
  2342. $attemptnumber = $submission->attemptnumber;
  2343. }
  2344. }
  2345. if ($attemptnumber >= 0) {
  2346. $params['attemptnumber'] = $attemptnumber;
  2347. }
  2348. $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
  2349. if ($grades) {
  2350. return reset($grades);
  2351. }
  2352. if ($create) {
  2353. $grade = new stdClass();
  2354. $grade->assignment = $this->get_instance()->id;
  2355. $grade->userid = $userid;
  2356. $grade->timecreated = time();
  2357. $grade->timemodified = $grade->timecreated;
  2358. $grade->grade = -1;
  2359. $grade->grader = $USER->id;
  2360. if ($attemptnumber >= 0) {
  2361. $grade->attemptnumber = $attemptnumber;
  2362. }
  2363. $gid = $DB->insert_record('assign_grades', $grade);
  2364. $grade->id = $gid;
  2365. return $grade;
  2366. }
  2367. return false;
  2368. }
  2369. /**
  2370. * This will retrieve a grade object from the db.
  2371. *
  2372. * @param int $gradeid The id of the grade
  2373. * @return stdClass The grade record
  2374. */
  2375. protected function get_grade($gradeid) {
  2376. global $DB;
  2377. $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
  2378. return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
  2379. }
  2380. /**
  2381. * Print the grading page for a single user submission.
  2382. *
  2383. * @param moodleform $mform
  2384. * @return string
  2385. */
  2386. protected function view_single_grade_page($mform) {
  2387. global $DB, $CFG;
  2388. $o = '';
  2389. $instance = $this->get_instance();
  2390. require_once($CFG->dirroot . '/mod/assign/gradeform.php');
  2391. // Need submit permission to submit an assignment.
  2392. require_capability('mod/assign:grade', $this->context);
  2393. $header = new assign_header($instance,
  2394. $this->get_context(),
  2395. false,
  2396. $this->get_course_module()->id,
  2397. get_string('grading', 'assign'));
  2398. $o .= $this->get_renderer()->render($header);
  2399. // If userid is passed - we are only grading a single student.
  2400. $rownum = required_param('rownum', PARAM_INT);
  2401. $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
  2402. $userid = optional_param('userid', 0, PARAM_INT);
  2403. $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
  2404. $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
  2405. if (!$userid) {
  2406. if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
  2407. $useridlist = $this->get_grading_userid_list();
  2408. }
  2409. $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
  2410. } else {
  2411. $rownum = 0;
  2412. $useridlist = array($userid);
  2413. }
  2414. if ($rownum < 0 || $rownum > count($useridlist)) {
  2415. throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
  2416. }
  2417. $last = false;
  2418. $userid = $useridlist[$rownum];
  2419. if ($rownum == count($useridlist) - 1) {
  2420. $last = true;
  2421. }
  2422. $user = $DB->get_record('user', array('id' => $userid));
  2423. if ($user) {
  2424. $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
  2425. $usersummary = new assign_user_summary($user,
  2426. $this->get_course()->id,
  2427. $viewfullnames,
  2428. $this->is_blind_marking(),
  2429. $this->get_uniqueid_for_user($user->id),
  2430. get_extra_user_fields($this->get_context()),
  2431. !$this->is_active_user($userid));
  2432. $o .= $this->get_renderer()->render($usersummary);
  2433. }
  2434. $submission = $this->get_user_submission($userid, false, $attemptnumber);
  2435. $submissiongroup = null;
  2436. $teamsubmission = null;
  2437. $notsubmitted = array();
  2438. if ($instance->teamsubmission) {
  2439. $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
  2440. $submissiongroup = $this->get_submission_group($userid);
  2441. $groupid = 0;
  2442. if ($submissiongroup) {
  2443. $groupid = $submissiongroup->id;
  2444. }
  2445. $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
  2446. }
  2447. // Get the requested grade.
  2448. $grade = $this->get_user_grade($userid, false, $attemptnumber);
  2449. $flags = $this->get_user_flags($userid, false);
  2450. if ($this->can_view_submission($userid)) {
  2451. $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
  2452. $extensionduedate = null;
  2453. if ($flags) {
  2454. $extensionduedate = $flags->extensionduedate;
  2455. }
  2456. $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
  2457. $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
  2458. $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
  2459. $instance->alwaysshowdescription,
  2460. $submission,
  2461. $instance->teamsubmission,
  2462. $teamsubmission,
  2463. $submissiongroup,
  2464. $notsubmitted,
  2465. $this->is_any_submission_plugin_enabled(),
  2466. $gradelocked,
  2467. $this->is_graded($userid),
  2468. $instance->duedate,
  2469. $instance->cutoffdate,
  2470. $this->get_submission_plugins(),
  2471. $this->get_return_action(),
  2472. $this->get_return_params(),
  2473. $this->get_course_module()->id,
  2474. $this->get_course()->id,
  2475. assign_submission_status::GRADER_VIEW,
  2476. $showedit,
  2477. false,
  2478. $viewfullnames,
  2479. $extensionduedate,
  2480. $this->get_context(),
  2481. $this->is_blind_marking(),
  2482. '',
  2483. $instance->attemptreopenmethod,
  2484. $instance->maxattempts);
  2485. $o .= $this->get_renderer()->render($submissionstatus);
  2486. }
  2487. if ($grade) {
  2488. $data = new stdClass();
  2489. if ($grade->grade !== null && $grade->grade >= 0) {
  2490. $data->grade = format_float($grade->grade, 2);
  2491. }
  2492. if (!empty($flags->workflowstate)) {
  2493. $data->workflowstate = $flags->workflowstate;
  2494. }
  2495. if (!empty($flags->allocatedmarker)) {
  2496. $data->allocatedmarker = $flags->allocatedmarker;
  2497. }
  2498. } else {
  2499. $data = new stdClass();
  2500. $data->grade = '';
  2501. }
  2502. // Warning if required.
  2503. $allsubmissions = $this->get_all_submissions($userid);
  2504. if ($attemptnumber != -1) {
  2505. $params = array('attemptnumber'=>$attemptnumber + 1,
  2506. 'totalattempts'=>count($allsubmissions));
  2507. $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
  2508. $o .= $this->get_renderer()->notification($message);
  2509. }
  2510. // Now show the grading form.
  2511. if (!$mform) {
  2512. $pagination = array('rownum'=>$rownum,
  2513. 'useridlistid'=>$useridlistid,
  2514. 'last'=>$last,
  2515. 'userid'=>optional_param('userid', 0, PARAM_INT),
  2516. 'attemptnumber'=>$attemptnumber);
  2517. $formparams = array($this, $data, $pagination);
  2518. $mform = new mod_assign_grade_form(null,
  2519. $formparams,
  2520. 'post',
  2521. '',
  2522. array('class'=>'gradeform'));
  2523. }
  2524. $o .= $this->get_renderer()->heading(get_string('grade'), 3);
  2525. $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
  2526. if (count($allsubmissions) > 1 && $attemptnumber == -1) {
  2527. $allgrades = $this->get_all_grades($userid);
  2528. $history = new assign_attempt_history($allsubmissions,
  2529. $allgrades,
  2530. $this->get_submission_plugins(),
  2531. $this->get_feedback_plugins(),
  2532. $this->get_course_module()->id,
  2533. $this->get_return_action(),
  2534. $this->get_return_params(),
  2535. true,
  2536. $useridlistid,
  2537. $rownum);
  2538. $o .= $this->get_renderer()->render($history);
  2539. }
  2540. $msg = get_string('viewgradingformforstudent',
  2541. 'assign',
  2542. array('id'=>$user->id, 'fullname'=>fullname($user)));
  2543. $this->add_to_log('view grading form', $msg);
  2544. $o .= $this->view_footer();
  2545. return $o;
  2546. }
  2547. /**
  2548. * Show a confirmation page to make sure they want to release student identities.
  2549. *
  2550. * @return string
  2551. */
  2552. protected function view_reveal_identities_confirm() {
  2553. global $CFG, $USER;
  2554. require_capability('mod/assign:revealidentities', $this->get_context());
  2555. $o = '';
  2556. $header = new assign_header($this->get_instance(),
  2557. $this->get_context(),
  2558. false,
  2559. $this->get_course_module()->id);
  2560. $o .= $this->get_renderer()->render($header);
  2561. $urlparams = array('id'=>$this->get_course_module()->id,
  2562. 'action'=>'revealidentitiesconfirm',
  2563. 'sesskey'=>sesskey());
  2564. $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
  2565. $urlparams = array('id'=>$this->get_course_module()->id,
  2566. 'action'=>'grading');
  2567. $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
  2568. $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
  2569. $confirmurl,
  2570. $cancelurl);
  2571. $o .= $this->view_footer();
  2572. $this->add_to_log('view', get_string('viewrevealidentitiesconfirm', 'assign'));
  2573. return $o;
  2574. }
  2575. /**
  2576. * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
  2577. *
  2578. * @return string
  2579. */
  2580. protected function view_return_links() {
  2581. $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
  2582. $returnparams = optional_param('returnparams', '', PARAM_TEXT);
  2583. $params = array();
  2584. $returnparams = str_replace('&amp;', '&', $returnparams);
  2585. parse_str($returnparams, $params);
  2586. $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
  2587. $params = array_merge($newparams, $params);
  2588. $url = new moodle_url('/mod/assign/view.php', $params);
  2589. return $this->get_renderer()->single_button($url, get_string('back'), 'get');
  2590. }
  2591. /**
  2592. * View the grading table of all submissions for this assignment.
  2593. *
  2594. * @return string
  2595. */
  2596. protected function view_grading_table() {
  2597. global $USER, $CFG;
  2598. // Include grading options form.
  2599. require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
  2600. require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
  2601. require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
  2602. $o = '';
  2603. $cmid = $this->get_course_module()->id;
  2604. $links = array();
  2605. if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
  2606. has_capability('moodle/grade:viewall', $this->get_course_context())) {
  2607. $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
  2608. $links[$gradebookurl] = get_string('viewgradebook', 'assign');
  2609. }
  2610. if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
  2611. $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
  2612. $links[$downloadurl] = get_string('downloadall', 'assign');
  2613. }
  2614. if ($this->is_blind_marking() &&
  2615. has_capability('mod/assign:revealidentities', $this->get_context())) {
  2616. $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
  2617. $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
  2618. }
  2619. foreach ($this->get_feedback_plugins() as $plugin) {
  2620. if ($plugin->is_enabled() && $plugin->is_visible()) {
  2621. foreach ($plugin->get_grading_actions() as $action => $description) {
  2622. $url = '/mod/assign/view.php' .
  2623. '?id=' . $cmid .
  2624. '&plugin=' . $plugin->get_type() .
  2625. '&pluginsubtype=assignfeedback' .
  2626. '&action=viewpluginpage&pluginaction=' . $action;
  2627. $links[$url] = $description;
  2628. }
  2629. }
  2630. }
  2631. // Sort links alphabetically based on the link description.
  2632. core_collator::asort($links);
  2633. $gradingactions = new url_select($links);
  2634. $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
  2635. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  2636. $perpage = get_user_preferences('assign_perpage', 10);
  2637. $filter = get_user_preferences('assign_filter', '');
  2638. $markerfilter = get_user_preferences('assign_markerfilter', '');
  2639. $workflowfilter = get_user_preferences('assign_workflowfilter', '');
  2640. $controller = $gradingmanager->get_active_controller();
  2641. $showquickgrading = empty($controller);
  2642. $quickgrading = get_user_preferences('assign_quickgrading', false);
  2643. $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
  2644. $markingallocation = $this->get_instance()->markingworkflow &&
  2645. $this->get_instance()->markingallocation &&
  2646. has_capability('mod/assign:manageallocations', $this->context);
  2647. // Get markers to use in drop lists.
  2648. $markingallocationoptions = array();
  2649. if ($markingallocation) {
  2650. $markers = get_users_by_capability($this->context, 'mod/assign:grade');
  2651. $markingallocationoptions[''] = get_string('filternone', 'assign');
  2652. foreach ($markers as $marker) {
  2653. $markingallocationoptions[$marker->id] = fullname($marker);
  2654. }
  2655. }
  2656. $markingworkflow = $this->get_instance()->markingworkflow;
  2657. // Get marking states to show in form.
  2658. $markingworkflowoptions = array();
  2659. if ($markingworkflow) {
  2660. $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
  2661. $markingworkflowoptions[''] = get_string('filternone', 'assign');
  2662. $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notmarked;
  2663. $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user());
  2664. }
  2665. // Print options for changing the filter and changing the number of results per page.
  2666. $gradingoptionsformparams = array('cm'=>$cmid,
  2667. 'contextid'=>$this->context->id,
  2668. 'userid'=>$USER->id,
  2669. 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
  2670. 'showquickgrading'=>$showquickgrading,
  2671. 'quickgrading'=>$quickgrading,
  2672. 'markingworkflowopt'=>$markingworkflowoptions,
  2673. 'markingallocationopt'=>$markingallocationoptions,
  2674. 'showonlyactiveenrolopt'=>$showonlyactiveenrolopt,
  2675. 'showonlyactiveenrol'=>$this->show_only_active_users());
  2676. $classoptions = array('class'=>'gradingoptionsform');
  2677. $gradingoptionsform = new mod_assign_grading_options_form(null,
  2678. $gradingoptionsformparams,
  2679. 'post',
  2680. '',
  2681. $classoptions);
  2682. $batchformparams = array('cm'=>$cmid,
  2683. 'submissiondrafts'=>$this->get_instance()->submissiondrafts,
  2684. 'duedate'=>$this->get_instance()->duedate,
  2685. 'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
  2686. 'feedbackplugins'=>$this->get_feedback_plugins(),
  2687. 'context'=>$this->get_context(),
  2688. 'markingworkflow'=>$markingworkflow,
  2689. 'markingallocation'=>$markingallocation);
  2690. $classoptions = array('class'=>'gradingbatchoperationsform');
  2691. $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
  2692. $batchformparams,
  2693. 'post',
  2694. '',
  2695. $classoptions);
  2696. $gradingoptionsdata = new stdClass();
  2697. $gradingoptionsdata->perpage = $perpage;
  2698. $gradingoptionsdata->filter = $filter;
  2699. $gradingoptionsdata->markerfilter = $markerfilter;
  2700. $gradingoptionsdata->workflowfilter = $workflowfilter;
  2701. $gradingoptionsform->set_data($gradingoptionsdata);
  2702. $actionformtext = $this->get_renderer()->render($gradingactions);
  2703. $header = new assign_header($this->get_instance(),
  2704. $this->get_context(),
  2705. false,
  2706. $this->get_course_module()->id,
  2707. get_string('grading', 'assign'),
  2708. $actionformtext);
  2709. $o .= $this->get_renderer()->render($header);
  2710. $currenturl = $CFG->wwwroot .
  2711. '/mod/assign/view.php?id=' .
  2712. $this->get_course_module()->id .
  2713. '&action=grading';
  2714. $o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true);
  2715. // Plagiarism update status apearring in the grading book.
  2716. if (!empty($CFG->enableplagiarism)) {
  2717. require_once($CFG->libdir . '/plagiarismlib.php');
  2718. $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
  2719. }
  2720. // Load and print the table of submissions.
  2721. if ($showquickgrading && $quickgrading) {
  2722. $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, true);
  2723. $table = $this->get_renderer()->render($gradingtable);
  2724. $page = optional_param('page', null, PARAM_INT);
  2725. $quickformparams = array('cm' => $this->get_course_module()->id,
  2726. 'gradingtable' => $table,
  2727. 'page' => $page);
  2728. $quickgradingform = new mod_assign_quick_grading_form(null, $quickformparams);
  2729. $o .= $this->get_renderer()->render(new assign_form('quickgradingform', $quickgradingform));
  2730. } else {
  2731. $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, false);
  2732. $o .= $this->get_renderer()->render($gradingtable);
  2733. }
  2734. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  2735. $users = array_keys($this->list_participants($currentgroup, true));
  2736. if (count($users) != 0) {
  2737. // If no enrolled user in a course then don't display the batch operations feature.
  2738. $assignform = new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform);
  2739. $o .= $this->get_renderer()->render($assignform);
  2740. }
  2741. $assignform = new assign_form('gradingoptionsform',
  2742. $gradingoptionsform,
  2743. 'M.mod_assign.init_grading_options');
  2744. $o .= $this->get_renderer()->render($assignform);
  2745. return $o;
  2746. }
  2747. /**
  2748. * View entire grading page.
  2749. *
  2750. * @return string
  2751. */
  2752. protected function view_grading_page() {
  2753. global $CFG;
  2754. $o = '';
  2755. // Need submit permission to submit an assignment.
  2756. require_capability('mod/assign:grade', $this->context);
  2757. require_once($CFG->dirroot . '/mod/assign/gradeform.php');
  2758. // Only load this if it is.
  2759. $o .= $this->view_grading_table();
  2760. $o .= $this->view_footer();
  2761. $logmessage = get_string('viewsubmissiongradingtable', 'assign');
  2762. $this->add_to_log('view submission grading table', $logmessage);
  2763. return $o;
  2764. }
  2765. /**
  2766. * Capture the output of the plagiarism plugins disclosures and return it as a string.
  2767. *
  2768. * @return void
  2769. */
  2770. protected function plagiarism_print_disclosure() {
  2771. global $CFG;
  2772. $o = '';
  2773. if (!empty($CFG->enableplagiarism)) {
  2774. require_once($CFG->libdir . '/plagiarismlib.php');
  2775. $o .= plagiarism_print_disclosure($this->get_course_module()->id);
  2776. }
  2777. return $o;
  2778. }
  2779. /**
  2780. * Message for students when assignment submissions have been closed.
  2781. *
  2782. * @return string
  2783. */
  2784. protected function view_student_error_message() {
  2785. global $CFG;
  2786. $o = '';
  2787. // Need submit permission to submit an assignment.
  2788. require_capability('mod/assign:submit', $this->context);
  2789. $header = new assign_header($this->get_instance(),
  2790. $this->get_context(),
  2791. $this->show_intro(),
  2792. $this->get_course_module()->id,
  2793. get_string('editsubmission', 'assign'));
  2794. $o .= $this->get_renderer()->render($header);
  2795. $o .= $this->get_renderer()->notification(get_string('submissionsclosed', 'assign'));
  2796. $o .= $this->view_footer();
  2797. return $o;
  2798. }
  2799. /**
  2800. * View edit submissions page.
  2801. *
  2802. * @param moodleform $mform
  2803. * @param array $notices A list of notices to display at the top of the
  2804. * edit submission form (e.g. from plugins).
  2805. * @return string The page output.
  2806. */
  2807. protected function view_edit_submission_page($mform, $notices) {
  2808. global $CFG;
  2809. $o = '';
  2810. require_once($CFG->dirroot . '/mod/assign/submission_form.php');
  2811. // Need submit permission to submit an assignment.
  2812. require_capability('mod/assign:submit', $this->context);
  2813. if (!$this->submissions_open()) {
  2814. return $this->view_student_error_message();
  2815. }
  2816. $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
  2817. $this->get_context(),
  2818. $this->show_intro(),
  2819. $this->get_course_module()->id,
  2820. get_string('editsubmission', 'assign')));
  2821. $o .= $this->plagiarism_print_disclosure();
  2822. $data = new stdClass();
  2823. if (!$mform) {
  2824. $mform = new mod_assign_submission_form(null, array($this, $data));
  2825. }
  2826. foreach ($notices as $notice) {
  2827. $o .= $this->get_renderer()->notification($notice);
  2828. }
  2829. $o .= $this->get_renderer()->render(new assign_form('editsubmissionform', $mform));
  2830. $o .= $this->view_footer();
  2831. $this->add_to_log('view submit assignment form', get_string('viewownsubmissionform', 'assign'));
  2832. return $o;
  2833. }
  2834. /**
  2835. * See if this assignment has a grade yet.
  2836. *
  2837. * @param int $userid
  2838. * @return bool
  2839. */
  2840. protected function is_graded($userid) {
  2841. $grade = $this->get_user_grade($userid, false);
  2842. if ($grade) {
  2843. return ($grade->grade !== null && $grade->grade >= 0);
  2844. }
  2845. return false;
  2846. }
  2847. /**
  2848. * Perform an access check to see if the current $USER can view this group submission.
  2849. *
  2850. * @param int $groupid
  2851. * @return bool
  2852. */
  2853. public function can_view_group_submission($groupid) {
  2854. global $USER;
  2855. if (has_capability('mod/assign:grade', $this->context)) {
  2856. return true;
  2857. }
  2858. if (!is_enrolled($this->get_course_context(), $USER->id)) {
  2859. return false;
  2860. }
  2861. $members = $this->get_submission_group_members($groupid, true);
  2862. foreach ($members as $member) {
  2863. if ($member->id == $USER->id) {
  2864. return true;
  2865. }
  2866. }
  2867. return false;
  2868. }
  2869. /**
  2870. * Perform an access check to see if the current $USER can view this users submission.
  2871. *
  2872. * @param int $userid
  2873. * @return bool
  2874. */
  2875. public function can_view_submission($userid) {
  2876. global $USER;
  2877. if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
  2878. return false;
  2879. }
  2880. if (has_capability('mod/assign:grade', $this->context)) {
  2881. return true;
  2882. }
  2883. if (!is_enrolled($this->get_course_context(), $userid)) {
  2884. return false;
  2885. }
  2886. if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) {
  2887. return true;
  2888. }
  2889. return false;
  2890. }
  2891. /**
  2892. * Allows the plugin to show a batch grading operation page.
  2893. *
  2894. * @param moodleform $mform
  2895. * @return none
  2896. */
  2897. protected function view_plugin_grading_batch_operation($mform) {
  2898. require_capability('mod/assign:grade', $this->context);
  2899. $prefix = 'plugingradingbatchoperation_';
  2900. if ($data = $mform->get_data()) {
  2901. $tail = substr($data->operation, strlen($prefix));
  2902. list($plugintype, $action) = explode('_', $tail, 2);
  2903. $plugin = $this->get_feedback_plugin_by_type($plugintype);
  2904. if ($plugin) {
  2905. $users = $data->selectedusers;
  2906. $userlist = explode(',', $users);
  2907. echo $plugin->grading_batch_operation($action, $userlist);
  2908. return;
  2909. }
  2910. }
  2911. print_error('invalidformdata', '');
  2912. }
  2913. /**
  2914. * Ask the user to confirm they want to perform this batch operation
  2915. *
  2916. * @param moodleform $mform Set to a grading batch operations form
  2917. * @return string - the page to view after processing these actions
  2918. */
  2919. protected function process_grading_batch_operation(& $mform) {
  2920. global $CFG;
  2921. require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
  2922. require_sesskey();
  2923. $markingallocation = $this->get_instance()->markingworkflow &&
  2924. $this->get_instance()->markingallocation &&
  2925. has_capability('mod/assign:manageallocations', $this->context);
  2926. $batchformparams = array('cm'=>$this->get_course_module()->id,
  2927. 'submissiondrafts'=>$this->get_instance()->submissiondrafts,
  2928. 'duedate'=>$this->get_instance()->duedate,
  2929. 'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
  2930. 'feedbackplugins'=>$this->get_feedback_plugins(),
  2931. 'context'=>$this->get_context(),
  2932. 'markingworkflow'=>$this->get_instance()->markingworkflow,
  2933. 'markingallocation'=>$markingallocation);
  2934. $formclasses = array('class'=>'gradingbatchoperationsform');
  2935. $mform = new mod_assign_grading_batch_operations_form(null,
  2936. $batchformparams,
  2937. 'post',
  2938. '',
  2939. $formclasses);
  2940. if ($data = $mform->get_data()) {
  2941. // Get the list of users.
  2942. $users = $data->selectedusers;
  2943. $userlist = explode(',', $users);
  2944. $prefix = 'plugingradingbatchoperation_';
  2945. if ($data->operation == 'grantextension') {
  2946. // Reset the form so the grant extension page will create the extension form.
  2947. $mform = null;
  2948. return 'grantextension';
  2949. } else if ($data->operation == 'setmarkingworkflowstate') {
  2950. return 'viewbatchsetmarkingworkflowstate';
  2951. } else if ($data->operation == 'setmarkingallocation') {
  2952. return 'viewbatchmarkingallocation';
  2953. } else if (strpos($data->operation, $prefix) === 0) {
  2954. $tail = substr($data->operation, strlen($prefix));
  2955. list($plugintype, $action) = explode('_', $tail, 2);
  2956. $plugin = $this->get_feedback_plugin_by_type($plugintype);
  2957. if ($plugin) {
  2958. return 'plugingradingbatchoperation';
  2959. }
  2960. }
  2961. foreach ($userlist as $userid) {
  2962. if ($data->operation == 'lock') {
  2963. $this->process_lock_submission($userid);
  2964. } else if ($data->operation == 'unlock') {
  2965. $this->process_unlock_submission($userid);
  2966. } else if ($data->operation == 'reverttodraft') {
  2967. $this->process_revert_to_draft($userid);
  2968. } else if ($data->operation == 'addattempt') {
  2969. if (!$this->get_instance()->teamsubmission) {
  2970. $this->process_add_attempt($userid);
  2971. }
  2972. }
  2973. }
  2974. if ($this->get_instance()->teamsubmission && $data->operation == 'addattempt') {
  2975. // This needs to be handled separately so that each team submission is only re-opened one time.
  2976. $this->process_add_attempt_group($userlist);
  2977. }
  2978. }
  2979. return 'grading';
  2980. }
  2981. /**
  2982. * Shows a form that allows the workflow state for selected submissions to be changed.
  2983. *
  2984. * @param moodleform $mform Set to a grading batch operations form
  2985. * @return string - the page to view after processing these actions
  2986. */
  2987. private function view_batch_set_workflow_state($mform) {
  2988. global $CFG, $DB;
  2989. require_once($CFG->dirroot . '/mod/assign/batchsetmarkingworkflowstateform.php');
  2990. $o = '';
  2991. $submitteddata = $mform->get_data();
  2992. $users = $submitteddata->selectedusers;
  2993. $userlist = explode(',', $users);
  2994. $formdata = array('id' => $this->get_course_module()->id,
  2995. 'selectedusers' => $users);
  2996. $usershtml = '';
  2997. $usercount = 0;
  2998. $extrauserfields = get_extra_user_fields($this->get_context());
  2999. foreach ($userlist as $userid) {
  3000. if ($usercount >= 5) {
  3001. $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
  3002. break;
  3003. }
  3004. $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
  3005. $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
  3006. $this->get_course()->id,
  3007. has_capability('moodle/site:viewfullnames',
  3008. $this->get_course_context()),
  3009. $this->is_blind_marking(),
  3010. $this->get_uniqueid_for_user($user->id),
  3011. $extrauserfields,
  3012. !$this->is_active_user($userid)));
  3013. $usercount += 1;
  3014. }
  3015. $formparams = array(
  3016. 'userscount' => count($userlist),
  3017. 'usershtml' => $usershtml,
  3018. 'markingworkflowstates' => $this->get_marking_workflow_states_for_current_user()
  3019. );
  3020. $mform = new mod_assign_batch_set_marking_workflow_state_form(null, $formparams);
  3021. $mform->set_data($formdata); // Initialises the hidden elements.
  3022. $o .= $this->get_renderer()->header();
  3023. $o .= $this->get_renderer()->render(new assign_form('setworkflowstate', $mform));
  3024. $o .= $this->view_footer();
  3025. $this->add_to_log('view batch set marking workflow state', get_string('viewbatchsetmarkingworkflowstate', 'assign'));
  3026. return $o;
  3027. }
  3028. /**
  3029. * Shows a form that allows the allocated marker for selected submissions to be changed.
  3030. *
  3031. * @param moodleform $mform Set to a grading batch operations form
  3032. * @return string - the page to view after processing these actions
  3033. */
  3034. private function view_batch_markingallocation($mform) {
  3035. global $CFG, $DB;
  3036. require_once($CFG->dirroot . '/mod/assign/batchsetallocatedmarkerform.php');
  3037. $o = '';
  3038. $submitteddata = $mform->get_data();
  3039. $users = $submitteddata->selectedusers;
  3040. $userlist = explode(',', $users);
  3041. $formdata = array('id' => $this->get_course_module()->id,
  3042. 'selectedusers' => $users);
  3043. $usershtml = '';
  3044. $usercount = 0;
  3045. $extrauserfields = get_extra_user_fields($this->get_context());
  3046. foreach ($userlist as $userid) {
  3047. if ($usercount >= 5) {
  3048. $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
  3049. break;
  3050. }
  3051. $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
  3052. $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
  3053. $this->get_course()->id,
  3054. has_capability('moodle/site:viewfullnames',
  3055. $this->get_course_context()),
  3056. $this->is_blind_marking(),
  3057. $this->get_uniqueid_for_user($user->id),
  3058. $extrauserfields,
  3059. !$this->is_active_user($userid)));
  3060. $usercount += 1;
  3061. }
  3062. $formparams = array(
  3063. 'userscount' => count($userlist),
  3064. 'usershtml' => $usershtml,
  3065. );
  3066. $markers = get_users_by_capability($this->get_context(), 'mod/assign:grade');
  3067. $markerlist = array();
  3068. foreach ($markers as $marker) {
  3069. $markerlist[$marker->id] = fullname($marker);
  3070. }
  3071. $formparams['markers'] = $markerlist;
  3072. $mform = new mod_assign_batch_set_allocatedmarker_form(null, $formparams);
  3073. $mform->set_data($formdata); // Initialises the hidden elements.
  3074. $o .= $this->get_renderer()->header();
  3075. $o .= $this->get_renderer()->render(new assign_form('setworkflowstate', $mform));
  3076. $o .= $this->view_footer();
  3077. $this->add_to_log('view batch set marker allocation', get_string('viewbatchmarkingallocation', 'assign'));
  3078. return $o;
  3079. }
  3080. /**
  3081. * Ask the user to confirm they want to submit their work for grading.
  3082. *
  3083. * @param moodleform $mform - null unless form validation has failed
  3084. * @return string
  3085. */
  3086. protected function check_submit_for_grading($mform) {
  3087. global $USER, $CFG;
  3088. require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
  3089. // Check that all of the submission plugins are ready for this submission.
  3090. $notifications = array();
  3091. $submission = $this->get_user_submission($USER->id, false);
  3092. $plugins = $this->get_submission_plugins();
  3093. foreach ($plugins as $plugin) {
  3094. if ($plugin->is_enabled() && $plugin->is_visible()) {
  3095. $check = $plugin->precheck_submission($submission);
  3096. if ($check !== true) {
  3097. $notifications[] = $check;
  3098. }
  3099. }
  3100. }
  3101. $data = new stdClass();
  3102. $adminconfig = $this->get_admin_config();
  3103. $requiresubmissionstatement = $this->get_instance()->requiresubmissionstatement &&
  3104. !empty($adminconfig->submissionstatement);
  3105. $submissionstatement = '';
  3106. if (!empty($adminconfig->submissionstatement)) {
  3107. $submissionstatement = $adminconfig->submissionstatement;
  3108. }
  3109. if ($mform == null) {
  3110. $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
  3111. $submissionstatement,
  3112. $this->get_course_module()->id,
  3113. $data));
  3114. }
  3115. $o = '';
  3116. $o .= $this->get_renderer()->header();
  3117. $submitforgradingpage = new assign_submit_for_grading_page($notifications,
  3118. $this->get_course_module()->id,
  3119. $mform);
  3120. $o .= $this->get_renderer()->render($submitforgradingpage);
  3121. $o .= $this->view_footer();
  3122. $logmessage = get_string('viewownsubmissionform', 'assign');
  3123. $this->add_to_log('view confirm submit assignment form', $logmessage);
  3124. return $o;
  3125. }
  3126. /**
  3127. * Print 2 tables of information with no action links -
  3128. * the submission summary and the grading summary.
  3129. *
  3130. * @param stdClass $user the user to print the report for
  3131. * @param bool $showlinks - Return plain text or links to the profile
  3132. * @return string - the html summary
  3133. */
  3134. public function view_student_summary($user, $showlinks) {
  3135. global $CFG, $DB, $PAGE;
  3136. $instance = $this->get_instance();
  3137. $grade = $this->get_user_grade($user->id, false);
  3138. $flags = $this->get_user_flags($user->id, false);
  3139. $submission = $this->get_user_submission($user->id, false);
  3140. $o = '';
  3141. $teamsubmission = null;
  3142. $submissiongroup = null;
  3143. $notsubmitted = array();
  3144. if ($instance->teamsubmission) {
  3145. $teamsubmission = $this->get_group_submission($user->id, 0, false);
  3146. $submissiongroup = $this->get_submission_group($user->id);
  3147. $groupid = 0;
  3148. if ($submissiongroup) {
  3149. $groupid = $submissiongroup->id;
  3150. }
  3151. $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
  3152. }
  3153. if ($this->can_view_submission($user->id)) {
  3154. $showedit = has_capability('mod/assign:submit', $this->context) &&
  3155. $this->submissions_open($user->id) &&
  3156. ($this->is_any_submission_plugin_enabled()) &&
  3157. $showlinks;
  3158. $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($user->id, false);
  3159. // Grading criteria preview.
  3160. $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
  3161. $gradingcontrollerpreview = '';
  3162. if ($gradingmethod = $gradingmanager->get_active_method()) {
  3163. $controller = $gradingmanager->get_controller($gradingmethod);
  3164. if ($controller->is_form_defined()) {
  3165. $gradingcontrollerpreview = $controller->render_preview($PAGE);
  3166. }
  3167. }
  3168. $showsubmit = ($showlinks && $this->submissions_open($user->id));
  3169. $showsubmit = ($showsubmit && $this->show_submit_button($submission, $teamsubmission));
  3170. $extensionduedate = null;
  3171. if ($flags) {
  3172. $extensionduedate = $flags->extensionduedate;
  3173. }
  3174. $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
  3175. $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
  3176. $instance->alwaysshowdescription,
  3177. $submission,
  3178. $instance->teamsubmission,
  3179. $teamsubmission,
  3180. $submissiongroup,
  3181. $notsubmitted,
  3182. $this->is_any_submission_plugin_enabled(),
  3183. $gradelocked,
  3184. $this->is_graded($user->id),
  3185. $instance->duedate,
  3186. $instance->cutoffdate,
  3187. $this->get_submission_plugins(),
  3188. $this->get_return_action(),
  3189. $this->get_return_params(),
  3190. $this->get_course_module()->id,
  3191. $this->get_course()->id,
  3192. assign_submission_status::STUDENT_VIEW,
  3193. $showedit,
  3194. $showsubmit,
  3195. $viewfullnames,
  3196. $extensionduedate,
  3197. $this->get_context(),
  3198. $this->is_blind_marking(),
  3199. $gradingcontrollerpreview,
  3200. $instance->attemptreopenmethod,
  3201. $instance->maxattempts);
  3202. $o .= $this->get_renderer()->render($submissionstatus);
  3203. require_once($CFG->libdir.'/gradelib.php');
  3204. require_once($CFG->dirroot.'/grade/grading/lib.php');
  3205. $gradinginfo = grade_get_grades($this->get_course()->id,
  3206. 'mod',
  3207. 'assign',
  3208. $instance->id,
  3209. $user->id);
  3210. $gradingitem = null;
  3211. $gradebookgrade = null;
  3212. if (isset($gradinginfo->items[0])) {
  3213. $gradingitem = $gradinginfo->items[0];
  3214. $gradebookgrade = $gradingitem->grades[$user->id];
  3215. }
  3216. // Check to see if all feedback plugins are empty.
  3217. $emptyplugins = true;
  3218. if ($grade) {
  3219. foreach ($this->get_feedback_plugins() as $plugin) {
  3220. if ($plugin->is_visible() && $plugin->is_enabled()) {
  3221. if (!$plugin->is_empty($grade)) {
  3222. $emptyplugins = false;
  3223. }
  3224. }
  3225. }
  3226. }
  3227. $gradereleased = true;
  3228. if ($this->get_instance()->markingworkflow &&
  3229. (empty($grade) || $flags->workflowstate != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED)) {
  3230. $gradereleased = false;
  3231. $emptyplugins = true; // Don't show feedback plugins until released either.
  3232. }
  3233. $cangrade = has_capability('mod/assign:grade', $this->get_context());
  3234. // If there is a visible grade, show the summary.
  3235. if ((!empty($gradebookgrade->grade) || !$emptyplugins)
  3236. && ($cangrade || !$gradebookgrade->hidden)) {
  3237. $gradefordisplay = null;
  3238. $gradeddate = null;
  3239. $grader = null;
  3240. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  3241. // Only show the grade if it is not hidden in gradebook.
  3242. if (!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) {
  3243. if ($controller = $gradingmanager->get_active_controller()) {
  3244. $menu = make_grades_menu($this->get_instance()->grade);
  3245. $controller->set_grade_range($menu, $this->get_instance()->grade > 0);
  3246. $gradefordisplay = $controller->render_grade($PAGE,
  3247. $grade->id,
  3248. $gradingitem,
  3249. $gradebookgrade->str_long_grade,
  3250. $cangrade);
  3251. } else {
  3252. $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
  3253. }
  3254. $gradeddate = $gradebookgrade->dategraded;
  3255. if (isset($grade->grader)) {
  3256. $grader = $DB->get_record('user', array('id'=>$grade->grader));
  3257. }
  3258. }
  3259. $feedbackstatus = new assign_feedback_status($gradefordisplay,
  3260. $gradeddate,
  3261. $grader,
  3262. $this->get_feedback_plugins(),
  3263. $grade,
  3264. $this->get_course_module()->id,
  3265. $this->get_return_action(),
  3266. $this->get_return_params());
  3267. $o .= $this->get_renderer()->render($feedbackstatus);
  3268. }
  3269. $allsubmissions = $this->get_all_submissions($user->id);
  3270. if (count($allsubmissions) > 1) {
  3271. $allgrades = $this->get_all_grades($user->id);
  3272. $history = new assign_attempt_history($allsubmissions,
  3273. $allgrades,
  3274. $this->get_submission_plugins(),
  3275. $this->get_feedback_plugins(),
  3276. $this->get_course_module()->id,
  3277. $this->get_return_action(),
  3278. $this->get_return_params(),
  3279. false,
  3280. 0,
  3281. 0);
  3282. $o .= $this->get_renderer()->render($history);
  3283. }
  3284. }
  3285. return $o;
  3286. }
  3287. /**
  3288. * Returns true if the submit subsission button should be shown to the user.
  3289. *
  3290. * @param stdClass $submission The users own submission record.
  3291. * @param stdClass $teamsubmission The users team submission record if there is one
  3292. * @return bool
  3293. */
  3294. protected function show_submit_button($submission = null, $teamsubmission = null) {
  3295. if ($teamsubmission) {
  3296. if ($teamsubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  3297. // The assignment submission has been completed.
  3298. return false;
  3299. } else if ($this->submission_empty($teamsubmission)) {
  3300. // There is nothing to submit yet.
  3301. return false;
  3302. } else if ($submission && $submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  3303. // The user has already clicked the submit button on the team submission.
  3304. return false;
  3305. }
  3306. } else if ($submission) {
  3307. if ($submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  3308. // The assignment submission has been completed.
  3309. return false;
  3310. } else if ($this->submission_empty($submission)) {
  3311. // There is nothing to submit.
  3312. return false;
  3313. }
  3314. } else {
  3315. // We've not got a valid submission or team submission.
  3316. return false;
  3317. }
  3318. // Last check is that this instance allows drafts.
  3319. return $this->get_instance()->submissiondrafts;
  3320. }
  3321. /**
  3322. * Get the grades for all previous attempts.
  3323. * For each grade - the grader is a full user record,
  3324. * and gradefordisplay is added (rendered from grading manager).
  3325. *
  3326. * @param int $userid If not set, $USER->id will be used.
  3327. * @return array $grades All grade records for this user.
  3328. */
  3329. protected function get_all_grades($userid) {
  3330. global $DB, $USER, $PAGE;
  3331. // If the userid is not null then use userid.
  3332. if (!$userid) {
  3333. $userid = $USER->id;
  3334. }
  3335. $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
  3336. $grades = $DB->get_records('assign_grades', $params, 'attemptnumber ASC');
  3337. $gradercache = array();
  3338. $cangrade = has_capability('mod/assign:grade', $this->get_context());
  3339. // Need gradingitem and gradingmanager.
  3340. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  3341. $controller = $gradingmanager->get_active_controller();
  3342. $gradinginfo = grade_get_grades($this->get_course()->id,
  3343. 'mod',
  3344. 'assign',
  3345. $this->get_instance()->id,
  3346. $userid);
  3347. $gradingitem = null;
  3348. if (isset($gradinginfo->items[0])) {
  3349. $gradingitem = $gradinginfo->items[0];
  3350. }
  3351. foreach ($grades as $grade) {
  3352. // First lookup the grader info.
  3353. if (isset($gradercache[$grade->grader])) {
  3354. $grade->grader = $gradercache[$grade->grader];
  3355. } else {
  3356. // Not in cache - need to load the grader record.
  3357. $grade->grader = $DB->get_record('user', array('id'=>$grade->grader));
  3358. $gradercache[$grade->grader->id] = $grade->grader;
  3359. }
  3360. // Now get the gradefordisplay.
  3361. if ($controller) {
  3362. $controller->set_grade_range(make_grades_menu($this->get_instance()->grade), $this->get_instance()->grade > 0);
  3363. $grade->gradefordisplay = $controller->render_grade($PAGE,
  3364. $grade->id,
  3365. $gradingitem,
  3366. $grade->grade,
  3367. $cangrade);
  3368. } else {
  3369. $grade->gradefordisplay = $this->display_grade($grade->grade, false);
  3370. }
  3371. }
  3372. return $grades;
  3373. }
  3374. /**
  3375. * Get the submissions for all previous attempts.
  3376. *
  3377. * @param int $userid If not set, $USER->id will be used.
  3378. * @return array $submissions All submission records for this user (or group).
  3379. */
  3380. protected function get_all_submissions($userid) {
  3381. global $DB, $USER;
  3382. // If the userid is not null then use userid.
  3383. if (!$userid) {
  3384. $userid = $USER->id;
  3385. }
  3386. $params = array();
  3387. if ($this->get_instance()->teamsubmission) {
  3388. $groupid = 0;
  3389. $group = $this->get_submission_group($userid);
  3390. if ($group) {
  3391. $groupid = $group->id;
  3392. }
  3393. // Params to get the group submissions.
  3394. $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
  3395. } else {
  3396. // Params to get the user submissions.
  3397. $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
  3398. }
  3399. // Return the submissions ordered by attempt.
  3400. $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber ASC');
  3401. return $submissions;
  3402. }
  3403. /**
  3404. * View submissions page (contains details of current submission).
  3405. *
  3406. * @return string
  3407. */
  3408. protected function view_submission_page() {
  3409. global $CFG, $DB, $USER, $PAGE;
  3410. $instance = $this->get_instance();
  3411. $o = '';
  3412. $o .= $this->get_renderer()->render(new assign_header($instance,
  3413. $this->get_context(),
  3414. $this->show_intro(),
  3415. $this->get_course_module()->id));
  3416. if ($this->can_grade()) {
  3417. $draft = ASSIGN_SUBMISSION_STATUS_DRAFT;
  3418. $submitted = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  3419. if ($instance->teamsubmission) {
  3420. $summary = new assign_grading_summary($this->count_teams(),
  3421. $instance->submissiondrafts,
  3422. $this->count_submissions_with_status($draft),
  3423. $this->is_any_submission_plugin_enabled(),
  3424. $this->count_submissions_with_status($submitted),
  3425. $instance->cutoffdate,
  3426. $instance->duedate,
  3427. $this->get_course_module()->id,
  3428. $this->count_submissions_need_grading(),
  3429. $instance->teamsubmission);
  3430. $o .= $this->get_renderer()->render($summary);
  3431. } else {
  3432. $summary = new assign_grading_summary($this->count_participants(0),
  3433. $instance->submissiondrafts,
  3434. $this->count_submissions_with_status($draft),
  3435. $this->is_any_submission_plugin_enabled(),
  3436. $this->count_submissions_with_status($submitted),
  3437. $instance->cutoffdate,
  3438. $instance->duedate,
  3439. $this->get_course_module()->id,
  3440. $this->count_submissions_need_grading(),
  3441. $instance->teamsubmission);
  3442. $o .= $this->get_renderer()->render($summary);
  3443. }
  3444. }
  3445. $grade = $this->get_user_grade($USER->id, false);
  3446. $submission = $this->get_user_submission($USER->id, false);
  3447. if ($this->can_view_submission($USER->id)) {
  3448. $o .= $this->view_student_summary($USER, true);
  3449. }
  3450. $o .= $this->view_footer();
  3451. $this->add_to_log('view', get_string('viewownsubmissionstatus', 'assign'));
  3452. return $o;
  3453. }
  3454. /**
  3455. * Convert the final raw grade(s) in the grading table for the gradebook.
  3456. *
  3457. * @param stdClass $grade
  3458. * @return array
  3459. */
  3460. protected function convert_grade_for_gradebook(stdClass $grade) {
  3461. $gradebookgrade = array();
  3462. if ($grade->grade >= 0) {
  3463. $gradebookgrade['rawgrade'] = $grade->grade;
  3464. }
  3465. // Allow "no grade" to be chosen.
  3466. if ($grade->grade == -1) {
  3467. $gradebookgrade['rawgrade'] = NULL;
  3468. }
  3469. $gradebookgrade['userid'] = $grade->userid;
  3470. $gradebookgrade['usermodified'] = $grade->grader;
  3471. $gradebookgrade['datesubmitted'] = null;
  3472. $gradebookgrade['dategraded'] = $grade->timemodified;
  3473. if (isset($grade->feedbackformat)) {
  3474. $gradebookgrade['feedbackformat'] = $grade->feedbackformat;
  3475. }
  3476. if (isset($grade->feedbacktext)) {
  3477. $gradebookgrade['feedback'] = $grade->feedbacktext;
  3478. }
  3479. return $gradebookgrade;
  3480. }
  3481. /**
  3482. * Convert submission details for the gradebook.
  3483. *
  3484. * @param stdClass $submission
  3485. * @return array
  3486. */
  3487. protected function convert_submission_for_gradebook(stdClass $submission) {
  3488. $gradebookgrade = array();
  3489. $gradebookgrade['userid'] = $submission->userid;
  3490. $gradebookgrade['usermodified'] = $submission->userid;
  3491. $gradebookgrade['datesubmitted'] = $submission->timemodified;
  3492. return $gradebookgrade;
  3493. }
  3494. /**
  3495. * Update grades in the gradebook.
  3496. *
  3497. * @param mixed $submission stdClass|null
  3498. * @param mixed $grade stdClass|null
  3499. * @return bool
  3500. */
  3501. protected function gradebook_item_update($submission=null, $grade=null) {
  3502. global $CFG;
  3503. require_once($CFG->dirroot.'/mod/assign/lib.php');
  3504. // Do not push grade to gradebook if blind marking is active as
  3505. // the gradebook would reveal the students.
  3506. if ($this->is_blind_marking()) {
  3507. return false;
  3508. }
  3509. // If marking workflow is enabled and grade is not released then don't send to gradebook yet.
  3510. if ($this->get_instance()->markingworkflow && !empty($grade)) {
  3511. $flags = $this->get_user_flags($grade->userid, false);
  3512. if (empty($flags->workflowstate) || $flags->workflowstate != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
  3513. return false;
  3514. }
  3515. }
  3516. if ($submission != null) {
  3517. if ($submission->userid == 0) {
  3518. // This is a group submission update.
  3519. $team = groups_get_members($submission->groupid, 'u.id');
  3520. foreach ($team as $member) {
  3521. $membersubmission = clone $submission;
  3522. $membersubmission->groupid = 0;
  3523. $membersubmission->userid = $member->id;
  3524. $this->gradebook_item_update($membersubmission, null);
  3525. }
  3526. return;
  3527. }
  3528. $gradebookgrade = $this->convert_submission_for_gradebook($submission);
  3529. } else {
  3530. $gradebookgrade = $this->convert_grade_for_gradebook($grade);
  3531. }
  3532. // Grading is disabled, return.
  3533. if ($this->grading_disabled($gradebookgrade['userid'])) {
  3534. return false;
  3535. }
  3536. $assign = clone $this->get_instance();
  3537. $assign->cmidnumber = $this->get_course_module()->idnumber;
  3538. return assign_grade_item_update($assign, $gradebookgrade);
  3539. }
  3540. /**
  3541. * Update team submission.
  3542. *
  3543. * @param stdClass $submission
  3544. * @param int $userid
  3545. * @param bool $updatetime
  3546. * @return bool
  3547. */
  3548. protected function update_team_submission(stdClass $submission, $userid, $updatetime) {
  3549. global $DB;
  3550. if ($updatetime) {
  3551. $submission->timemodified = time();
  3552. }
  3553. // First update the submission for the current user.
  3554. $mysubmission = $this->get_user_submission($userid, true, $submission->attemptnumber);
  3555. $mysubmission->status = $submission->status;
  3556. $this->update_submission($mysubmission, 0, $updatetime, false);
  3557. // Now check the team settings to see if this assignment qualifies as submitted or draft.
  3558. $team = $this->get_submission_group_members($submission->groupid, true);
  3559. $allsubmitted = true;
  3560. $anysubmitted = false;
  3561. $result = true;
  3562. if ($submission->status != ASSIGN_SUBMISSION_STATUS_REOPENED) {
  3563. foreach ($team as $member) {
  3564. $membersubmission = $this->get_user_submission($member->id, false, $submission->attemptnumber);
  3565. // If no submission found for team member and member is active then everyone has not submitted.
  3566. if (!$membersubmission || $membersubmission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED
  3567. && ($this->is_active_user($member->id))) {
  3568. $allsubmitted = false;
  3569. if ($anysubmitted) {
  3570. break;
  3571. }
  3572. } else {
  3573. $anysubmitted = true;
  3574. }
  3575. }
  3576. if ($this->get_instance()->requireallteammemberssubmit) {
  3577. if ($allsubmitted) {
  3578. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  3579. } else {
  3580. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  3581. }
  3582. $result = $DB->update_record('assign_submission', $submission);
  3583. } else {
  3584. if ($anysubmitted) {
  3585. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  3586. } else {
  3587. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  3588. }
  3589. $result = $DB->update_record('assign_submission', $submission);
  3590. }
  3591. } else {
  3592. // Set the group submission to reopened.
  3593. foreach ($team as $member) {
  3594. $membersubmission = $this->get_user_submission($member->id, true, $submission->attemptnumber);
  3595. $membersubmission->status = ASSIGN_SUBMISSION_STATUS_REOPENED;
  3596. $result = $DB->update_record('assign_submission', $membersubmission) && $result;
  3597. }
  3598. $result = $DB->update_record('assign_submission', $submission) && $result;
  3599. }
  3600. $this->gradebook_item_update($submission);
  3601. return $result;
  3602. }
  3603. /**
  3604. * Update grades in the gradebook based on submission time.
  3605. *
  3606. * @param stdClass $submission
  3607. * @param int $userid
  3608. * @param bool $updatetime
  3609. * @param bool $teamsubmission
  3610. * @return bool
  3611. */
  3612. protected function update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
  3613. global $DB;
  3614. if ($teamsubmission) {
  3615. return $this->update_team_submission($submission, $userid, $updatetime);
  3616. }
  3617. if ($updatetime) {
  3618. $submission->timemodified = time();
  3619. }
  3620. $result= $DB->update_record('assign_submission', $submission);
  3621. if ($result) {
  3622. $this->gradebook_item_update($submission);
  3623. }
  3624. return $result;
  3625. }
  3626. /**
  3627. * Is this assignment open for submissions?
  3628. *
  3629. * Check the due date,
  3630. * prevent late submissions,
  3631. * has this person already submitted,
  3632. * is the assignment locked?
  3633. *
  3634. * @param int $userid - Optional userid so we can see if a different user can submit
  3635. * @return bool
  3636. */
  3637. public function submissions_open($userid = 0) {
  3638. global $USER;
  3639. if (!$userid) {
  3640. $userid = $USER->id;
  3641. }
  3642. $time = time();
  3643. $dateopen = true;
  3644. $finaldate = false;
  3645. if ($this->get_instance()->cutoffdate) {
  3646. $finaldate = $this->get_instance()->cutoffdate;
  3647. }
  3648. $flags = $this->get_user_flags($userid, false);
  3649. if ($flags && $flags->locked) {
  3650. return false;
  3651. }
  3652. // User extensions.
  3653. if ($finaldate) {
  3654. if ($flags && $flags->extensionduedate) {
  3655. // Extension can be before cut off date.
  3656. if ($flags->extensionduedate > $finaldate) {
  3657. $finaldate = $flags->extensionduedate;
  3658. }
  3659. }
  3660. }
  3661. if ($finaldate) {
  3662. $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $finaldate);
  3663. } else {
  3664. $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
  3665. }
  3666. if (!$dateopen) {
  3667. return false;
  3668. }
  3669. // Now check if this user has already submitted etc.
  3670. if (!is_enrolled($this->get_course_context(), $userid)) {
  3671. return false;
  3672. }
  3673. $submission = false;
  3674. if ($this->get_instance()->teamsubmission) {
  3675. $submission = $this->get_group_submission($userid, 0, false);
  3676. } else {
  3677. $submission = $this->get_user_submission($userid, false);
  3678. }
  3679. if ($submission) {
  3680. if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  3681. // Drafts are tracked and the student has submitted the assignment.
  3682. return false;
  3683. }
  3684. }
  3685. // See if this user grade is locked in the gradebook.
  3686. $gradinginfo = grade_get_grades($this->get_course()->id,
  3687. 'mod',
  3688. 'assign',
  3689. $this->get_instance()->id,
  3690. array($userid));
  3691. if ($gradinginfo &&
  3692. isset($gradinginfo->items[0]->grades[$userid]) &&
  3693. $gradinginfo->items[0]->grades[$userid]->locked) {
  3694. return false;
  3695. }
  3696. return true;
  3697. }
  3698. /**
  3699. * Render the files in file area.
  3700. *
  3701. * @param string $component
  3702. * @param string $area
  3703. * @param int $submissionid
  3704. * @return string
  3705. */
  3706. public function render_area_files($component, $area, $submissionid) {
  3707. global $USER;
  3708. $fs = get_file_storage();
  3709. $browser = get_file_browser();
  3710. $files = $fs->get_area_files($this->get_context()->id,
  3711. $component,
  3712. $area,
  3713. $submissionid,
  3714. 'timemodified',
  3715. false);
  3716. return $this->get_renderer()->assign_files($this->context, $submissionid, $area, $component);
  3717. }
  3718. /**
  3719. * Returns a list of teachers that should be grading given submission.
  3720. *
  3721. * @param int $userid The submission to grade
  3722. * @return array
  3723. */
  3724. protected function get_graders($userid) {
  3725. // Potential graders should be active users only.
  3726. $potentialgraders = get_enrolled_users($this->context, "mod/assign:grade", null, 'u.*', null, null, null, true);
  3727. $graders = array();
  3728. if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) {
  3729. if ($groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_course_module()->groupingid)) {
  3730. foreach ($groups as $group) {
  3731. foreach ($potentialgraders as $grader) {
  3732. if ($grader->id == $userid) {
  3733. // Do not send self.
  3734. continue;
  3735. }
  3736. if (groups_is_member($group->id, $grader->id)) {
  3737. $graders[$grader->id] = $grader;
  3738. }
  3739. }
  3740. }
  3741. } else {
  3742. // User not in group, try to find graders without group.
  3743. foreach ($potentialgraders as $grader) {
  3744. if ($grader->id == $userid) {
  3745. // Do not send self.
  3746. continue;
  3747. }
  3748. if (!groups_has_membership($this->get_course_module(), $grader->id)) {
  3749. $graders[$grader->id] = $grader;
  3750. }
  3751. }
  3752. }
  3753. } else {
  3754. foreach ($potentialgraders as $grader) {
  3755. if ($grader->id == $userid) {
  3756. // Do not send self.
  3757. continue;
  3758. }
  3759. // Must be enrolled.
  3760. if (is_enrolled($this->get_course_context(), $grader->id)) {
  3761. $graders[$grader->id] = $grader;
  3762. }
  3763. }
  3764. }
  3765. return $graders;
  3766. }
  3767. /**
  3768. * Format a notification for plain text.
  3769. *
  3770. * @param string $messagetype
  3771. * @param stdClass $info
  3772. * @param stdClass $course
  3773. * @param stdClass $context
  3774. * @param string $modulename
  3775. * @param string $assignmentname
  3776. */
  3777. protected static function format_notification_message_text($messagetype,
  3778. $info,
  3779. $course,
  3780. $context,
  3781. $modulename,
  3782. $assignmentname) {
  3783. $formatparams = array('context' => $context->get_course_context());
  3784. $posttext = format_string($course->shortname, true, $formatparams) .
  3785. ' -> ' .
  3786. $modulename .
  3787. ' -> ' .
  3788. format_string($assignmentname, true, $formatparams) . "\n";
  3789. $posttext .= '---------------------------------------------------------------------' . "\n";
  3790. $posttext .= get_string($messagetype . 'text', 'assign', $info)."\n";
  3791. $posttext .= "\n---------------------------------------------------------------------\n";
  3792. return $posttext;
  3793. }
  3794. /**
  3795. * Format a notification for HTML.
  3796. *
  3797. * @param string $messagetype
  3798. * @param stdClass $info
  3799. * @param stdClass $course
  3800. * @param stdClass $context
  3801. * @param string $modulename
  3802. * @param stdClass $coursemodule
  3803. * @param string $assignmentname
  3804. */
  3805. protected static function format_notification_message_html($messagetype,
  3806. $info,
  3807. $course,
  3808. $context,
  3809. $modulename,
  3810. $coursemodule,
  3811. $assignmentname) {
  3812. global $CFG;
  3813. $formatparams = array('context' => $context->get_course_context());
  3814. $posthtml = '<p><font face="sans-serif">' .
  3815. '<a href="' . $CFG->wwwroot . '/course/view.php?id=' . $course->id . '">' .
  3816. format_string($course->shortname, true, $formatparams) .
  3817. '</a> ->' .
  3818. '<a href="' . $CFG->wwwroot . '/mod/assign/index.php?id=' . $course->id . '">' .
  3819. $modulename .
  3820. '</a> ->' .
  3821. '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $coursemodule->id . '">' .
  3822. format_string($assignmentname, true, $formatparams) .
  3823. '</a></font></p>';
  3824. $posthtml .= '<hr /><font face="sans-serif">';
  3825. $posthtml .= '<p>' . get_string($messagetype . 'html', 'assign', $info) . '</p>';
  3826. $posthtml .= '</font><hr />';
  3827. return $posthtml;
  3828. }
  3829. /**
  3830. * Message someone about something (static so it can be called from cron).
  3831. *
  3832. * @param stdClass $userfrom
  3833. * @param stdClass $userto
  3834. * @param string $messagetype
  3835. * @param string $eventtype
  3836. * @param int $updatetime
  3837. * @param stdClass $coursemodule
  3838. * @param stdClass $context
  3839. * @param stdClass $course
  3840. * @param string $modulename
  3841. * @param string $assignmentname
  3842. * @param bool $blindmarking
  3843. * @param int $uniqueidforuser
  3844. * @return void
  3845. */
  3846. public static function send_assignment_notification($userfrom,
  3847. $userto,
  3848. $messagetype,
  3849. $eventtype,
  3850. $updatetime,
  3851. $coursemodule,
  3852. $context,
  3853. $course,
  3854. $modulename,
  3855. $assignmentname,
  3856. $blindmarking,
  3857. $uniqueidforuser) {
  3858. global $CFG;
  3859. $info = new stdClass();
  3860. if ($blindmarking) {
  3861. $userfrom = clone($userfrom);
  3862. $info->username = get_string('participant', 'assign') . ' ' . $uniqueidforuser;
  3863. $userfrom->firstname = get_string('participant', 'assign');
  3864. $userfrom->lastname = $uniqueidforuser;
  3865. $userfrom->email = $CFG->noreplyaddress;
  3866. } else {
  3867. $info->username = fullname($userfrom, true);
  3868. }
  3869. $info->assignment = format_string($assignmentname, true, array('context'=>$context));
  3870. $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
  3871. $info->timeupdated = userdate($updatetime, get_string('strftimerecentfull'));
  3872. $postsubject = get_string($messagetype . 'small', 'assign', $info);
  3873. $posttext = self::format_notification_message_text($messagetype,
  3874. $info,
  3875. $course,
  3876. $context,
  3877. $modulename,
  3878. $assignmentname);
  3879. $posthtml = '';
  3880. if ($userto->mailformat == 1) {
  3881. $posthtml = self::format_notification_message_html($messagetype,
  3882. $info,
  3883. $course,
  3884. $context,
  3885. $modulename,
  3886. $coursemodule,
  3887. $assignmentname);
  3888. }
  3889. $eventdata = new stdClass();
  3890. $eventdata->modulename = 'assign';
  3891. $eventdata->userfrom = $userfrom;
  3892. $eventdata->userto = $userto;
  3893. $eventdata->subject = $postsubject;
  3894. $eventdata->fullmessage = $posttext;
  3895. $eventdata->fullmessageformat = FORMAT_PLAIN;
  3896. $eventdata->fullmessagehtml = $posthtml;
  3897. $eventdata->smallmessage = $postsubject;
  3898. $eventdata->name = $eventtype;
  3899. $eventdata->component = 'mod_assign';
  3900. $eventdata->notification = 1;
  3901. $eventdata->contexturl = $info->url;
  3902. $eventdata->contexturlname = $info->assignment;
  3903. message_send($eventdata);
  3904. }
  3905. /**
  3906. * Message someone about something.
  3907. *
  3908. * @param stdClass $userfrom
  3909. * @param stdClass $userto
  3910. * @param string $messagetype
  3911. * @param string $eventtype
  3912. * @param int $updatetime
  3913. * @return void
  3914. */
  3915. public function send_notification($userfrom,
  3916. $userto,
  3917. $messagetype,
  3918. $eventtype,
  3919. $updatetime) {
  3920. self::send_assignment_notification($userfrom,
  3921. $userto,
  3922. $messagetype,
  3923. $eventtype,
  3924. $updatetime,
  3925. $this->get_course_module(),
  3926. $this->get_context(),
  3927. $this->get_course(),
  3928. $this->get_module_name(),
  3929. $this->get_instance()->name,
  3930. $this->is_blind_marking(),
  3931. $this->get_uniqueid_for_user($userfrom->id));
  3932. }
  3933. /**
  3934. * Notify student upon successful submission copy.
  3935. *
  3936. * @param stdClass $submission
  3937. * @return void
  3938. */
  3939. protected function notify_student_submission_copied(stdClass $submission) {
  3940. global $DB, $USER;
  3941. $adminconfig = $this->get_admin_config();
  3942. // Use the same setting for this - no need for another one.
  3943. if (empty($adminconfig->submissionreceipts)) {
  3944. // No need to do anything.
  3945. return;
  3946. }
  3947. if ($submission->userid) {
  3948. $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
  3949. } else {
  3950. $user = $USER;
  3951. }
  3952. $this->send_notification($user,
  3953. $user,
  3954. 'submissioncopied',
  3955. 'assign_notification',
  3956. $submission->timemodified);
  3957. }
  3958. /**
  3959. * Notify student upon successful submission.
  3960. *
  3961. * @param stdClass $submission
  3962. * @return void
  3963. */
  3964. protected function notify_student_submission_receipt(stdClass $submission) {
  3965. global $DB, $USER;
  3966. $adminconfig = $this->get_admin_config();
  3967. if (empty($adminconfig->submissionreceipts)) {
  3968. // No need to do anything.
  3969. return;
  3970. }
  3971. if ($submission->userid) {
  3972. $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
  3973. } else {
  3974. $user = $USER;
  3975. }
  3976. $this->send_notification($user,
  3977. $user,
  3978. 'submissionreceipt',
  3979. 'assign_notification',
  3980. $submission->timemodified);
  3981. }
  3982. /**
  3983. * Send notifications to graders upon student submissions.
  3984. *
  3985. * @param stdClass $submission
  3986. * @return void
  3987. */
  3988. protected function notify_graders(stdClass $submission) {
  3989. global $DB, $USER;
  3990. $instance = $this->get_instance();
  3991. $late = $instance->duedate && ($instance->duedate < time());
  3992. if (!$instance->sendnotifications && !($late && $instance->sendlatenotifications)) {
  3993. // No need to do anything.
  3994. return;
  3995. }
  3996. if ($submission->userid) {
  3997. $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
  3998. } else {
  3999. $user = $USER;
  4000. }
  4001. if ($teachers = $this->get_graders($user->id)) {
  4002. foreach ($teachers as $teacher) {
  4003. $this->send_notification($user,
  4004. $teacher,
  4005. 'gradersubmissionupdated',
  4006. 'assign_notification',
  4007. $submission->timemodified);
  4008. }
  4009. }
  4010. }
  4011. /**
  4012. * Submit a submission for grading.
  4013. *
  4014. * @return bool Return false if the submission was not submitted.
  4015. */
  4016. public function submit_for_grading($data) {
  4017. global $USER;
  4018. // Need submit permission to submit an assignment.
  4019. require_capability('mod/assign:submit', $this->context);
  4020. $instance = $this->get_instance();
  4021. if ($instance->teamsubmission) {
  4022. $submission = $this->get_group_submission($USER->id, 0, true);
  4023. } else {
  4024. $submission = $this->get_user_submission($USER->id, true);
  4025. }
  4026. if (!$this->submissions_open($USER->id)) {
  4027. return false;
  4028. }
  4029. if ($instance->requiresubmissionstatement && !$data->submissionstatement) {
  4030. return false;
  4031. }
  4032. if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  4033. // Give each submission plugin a chance to process the submission.
  4034. $plugins = $this->get_submission_plugins();
  4035. foreach ($plugins as $plugin) {
  4036. if ($plugin->is_enabled() && $plugin->is_visible()) {
  4037. $plugin->submit_for_grading($submission);
  4038. }
  4039. }
  4040. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  4041. $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
  4042. $completion = new completion_info($this->get_course());
  4043. if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
  4044. $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
  4045. }
  4046. if (!empty($data->submissionstatement)) {
  4047. $logmessage = get_string('submissionstatementacceptedlog',
  4048. 'mod_assign',
  4049. fullname($USER));
  4050. $this->add_to_log('submission statement accepted', $logmessage);
  4051. }
  4052. $logdata = $this->add_to_log('submit for grading', $this->format_submission_for_log($submission), '', true);
  4053. $this->notify_graders($submission);
  4054. $this->notify_student_submission_receipt($submission);
  4055. // Trigger assessable_submitted event on submission.
  4056. $params = array(
  4057. 'context' => context_module::instance($this->get_course_module()->id),
  4058. 'objectid' => $submission->id,
  4059. 'other' => array(
  4060. 'submission_editable' => false
  4061. )
  4062. );
  4063. $event = \mod_assign\event\assessable_submitted::create($params);
  4064. $event->set_legacy_logdata($logdata);
  4065. $event->trigger();
  4066. return true;
  4067. }
  4068. return false;
  4069. }
  4070. /**
  4071. * Assignment submission is processed before grading.
  4072. *
  4073. * @param moodleform|null $mform If validation failed when submitting this form - this is the moodleform.
  4074. * It can be null.
  4075. * @return bool Return false if the validation fails. This affects which page is displayed next.
  4076. */
  4077. protected function process_submit_for_grading($mform) {
  4078. global $USER, $CFG;
  4079. require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
  4080. require_sesskey();
  4081. if (!$this->submissions_open()) {
  4082. return $this->view_student_error_message();
  4083. }
  4084. $instance = $this->get_instance();
  4085. $data = new stdClass();
  4086. $adminconfig = $this->get_admin_config();
  4087. $requiresubmissionstatement = $instance->requiresubmissionstatement &&
  4088. !empty($adminconfig->submissionstatement);
  4089. $submissionstatement = '';
  4090. if (!empty($adminconfig->submissionstatement)) {
  4091. $submissionstatement = $adminconfig->submissionstatement;
  4092. }
  4093. if ($mform == null) {
  4094. $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
  4095. $submissionstatement,
  4096. $this->get_course_module()->id,
  4097. $data));
  4098. }
  4099. $data = $mform->get_data();
  4100. if (!$mform->is_cancelled()) {
  4101. if ($mform->get_data() == false) {
  4102. return false;
  4103. }
  4104. return $this->submit_for_grading($data);
  4105. }
  4106. return true;
  4107. }
  4108. /**
  4109. * Save the extension date for a single user.
  4110. *
  4111. * @param int $userid The user id
  4112. * @param mixed $extensionduedate Either an integer date or null
  4113. * @return boolean
  4114. */
  4115. public function save_user_extension($userid, $extensionduedate) {
  4116. global $DB;
  4117. // Need submit permission to submit an assignment.
  4118. require_capability('mod/assign:grantextension', $this->context);
  4119. if (!is_enrolled($this->get_course_context(), $userid)) {
  4120. return false;
  4121. }
  4122. if (!has_capability('mod/assign:submit', $this->context, $userid)) {
  4123. return false;
  4124. }
  4125. if ($this->get_instance()->duedate && $extensionduedate) {
  4126. if ($this->get_instance()->duedate > $extensionduedate) {
  4127. return false;
  4128. }
  4129. }
  4130. if ($this->get_instance()->allowsubmissionsfromdate && $extensionduedate) {
  4131. if ($this->get_instance()->allowsubmissionsfromdate > $extensionduedate) {
  4132. return false;
  4133. }
  4134. }
  4135. $flags = $this->get_user_flags($userid, true);
  4136. $flags->extensionduedate = $extensionduedate;
  4137. $result = $this->update_user_flags($flags);
  4138. if ($result) {
  4139. $addtolog = $this->add_to_log('grant extension', $userid, '', true);
  4140. $params = array(
  4141. 'context' => $this->context,
  4142. 'objectid' => $flags->assignment,
  4143. 'relateduserid' => $userid
  4144. );
  4145. $event = \mod_assign\event\extension_granted::create($params);
  4146. $event->set_legacy_logdata($addtolog);
  4147. $event->trigger();
  4148. }
  4149. return $result;
  4150. }
  4151. /**
  4152. * Save extension date.
  4153. *
  4154. * @param moodleform $mform The submitted form
  4155. * @return boolean
  4156. */
  4157. protected function process_save_extension(& $mform) {
  4158. global $DB, $CFG;
  4159. // Include extension form.
  4160. require_once($CFG->dirroot . '/mod/assign/extensionform.php');
  4161. require_sesskey();
  4162. $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
  4163. $userid = 0;
  4164. if (!$batchusers) {
  4165. $userid = required_param('userid', PARAM_INT);
  4166. $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
  4167. }
  4168. $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
  4169. $userid,
  4170. $batchusers,
  4171. $this->get_instance(),
  4172. null));
  4173. if ($mform->is_cancelled()) {
  4174. return true;
  4175. }
  4176. if ($formdata = $mform->get_data()) {
  4177. if ($batchusers) {
  4178. $users = explode(',', $batchusers);
  4179. $result = true;
  4180. foreach ($users as $userid) {
  4181. $result = $this->save_user_extension($userid, $formdata->extensionduedate) && $result;
  4182. }
  4183. return $result;
  4184. } else {
  4185. return $this->save_user_extension($userid, $formdata->extensionduedate);
  4186. }
  4187. }
  4188. return false;
  4189. }
  4190. /**
  4191. * Save quick grades.
  4192. *
  4193. * @return string The result of the save operation
  4194. */
  4195. protected function process_save_quick_grades() {
  4196. global $USER, $DB, $CFG;
  4197. // Need grade permission.
  4198. require_capability('mod/assign:grade', $this->context);
  4199. require_sesskey();
  4200. // Make sure advanced grading is disabled.
  4201. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  4202. $controller = $gradingmanager->get_active_controller();
  4203. if (!empty($controller)) {
  4204. return get_string('errorquickgradingvsadvancedgrading', 'assign');
  4205. }
  4206. $users = array();
  4207. // First check all the last modified values.
  4208. $currentgroup = groups_get_activity_group($this->get_course_module(), true);
  4209. $participants = $this->list_participants($currentgroup, true);
  4210. // Gets a list of possible users and look for values based upon that.
  4211. foreach ($participants as $userid => $unused) {
  4212. $modified = optional_param('grademodified_' . $userid, -1, PARAM_INT);
  4213. // Gather the userid, updated grade and last modified value.
  4214. $record = new stdClass();
  4215. $record->userid = $userid;
  4216. if ($modified >= 0) {
  4217. $record->grade = unformat_float(optional_param('quickgrade_' . $record->userid, -1, PARAM_TEXT));
  4218. $record->workflowstate = optional_param('quickgrade_' . $record->userid.'_workflowstate', false, PARAM_TEXT);
  4219. $record->allocatedmarker = optional_param('quickgrade_' . $record->userid.'_allocatedmarker', false, PARAM_INT);
  4220. } else {
  4221. // This user was not in the grading table.
  4222. continue;
  4223. }
  4224. $record->lastmodified = $modified;
  4225. $record->gradinginfo = grade_get_grades($this->get_course()->id,
  4226. 'mod',
  4227. 'assign',
  4228. $this->get_instance()->id,
  4229. array($userid));
  4230. $users[$userid] = $record;
  4231. }
  4232. if (empty($users)) {
  4233. return get_string('nousersselected', 'assign');
  4234. }
  4235. list($userids, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
  4236. $params['assignid1'] = $this->get_instance()->id;
  4237. $params['assignid2'] = $this->get_instance()->id;
  4238. // Check them all for currency.
  4239. $grademaxattempt = 'SELECT mxg.userid, MAX(mxg.attemptnumber) AS maxattempt
  4240. FROM {assign_grades} mxg
  4241. WHERE mxg.assignment = :assignid1 GROUP BY mxg.userid';
  4242. $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified, uf.workflowstate, uf.allocatedmarker
  4243. FROM {user} u
  4244. LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid
  4245. LEFT JOIN {assign_grades} g ON
  4246. u.id = g.userid AND
  4247. g.assignment = :assignid2 AND
  4248. g.attemptnumber = gmx.maxattempt
  4249. LEFT JOIN {assign_user_flags} uf ON uf.assignment = g.assignment AND uf.userid = g.userid
  4250. WHERE u.id ' . $userids;
  4251. $currentgrades = $DB->get_recordset_sql($sql, $params);
  4252. $modifiedusers = array();
  4253. foreach ($currentgrades as $current) {
  4254. $modified = $users[(int)$current->userid];
  4255. $grade = $this->get_user_grade($modified->userid, false);
  4256. // Check to see if the grade column was even visible.
  4257. $gradecolpresent = optional_param('quickgrade_' . $modified->userid, false, PARAM_INT) !== false;
  4258. // Check to see if the outcomes were modified.
  4259. if ($CFG->enableoutcomes) {
  4260. foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
  4261. $oldoutcome = $outcome->grades[$modified->userid]->grade;
  4262. $paramname = 'outcome_' . $outcomeid . '_' . $modified->userid;
  4263. $newoutcome = optional_param($paramname, -1, PARAM_FLOAT);
  4264. // Check to see if the outcome column was even visible.
  4265. $outcomecolpresent = optional_param($paramname, false, PARAM_FLOAT) !== false;
  4266. if ($outcomecolpresent && ($oldoutcome != $newoutcome)) {
  4267. // Can't check modified time for outcomes because it is not reported.
  4268. $modifiedusers[$modified->userid] = $modified;
  4269. continue;
  4270. }
  4271. }
  4272. }
  4273. // Let plugins participate.
  4274. foreach ($this->feedbackplugins as $plugin) {
  4275. if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
  4276. // The plugins must handle is_quickgrading_modified correctly - ie
  4277. // handle hidden columns.
  4278. if ($plugin->is_quickgrading_modified($modified->userid, $grade)) {
  4279. if ((int)$current->lastmodified > (int)$modified->lastmodified) {
  4280. return get_string('errorrecordmodified', 'assign');
  4281. } else {
  4282. $modifiedusers[$modified->userid] = $modified;
  4283. continue;
  4284. }
  4285. }
  4286. }
  4287. }
  4288. if (($current->grade < 0 || $current->grade === null) &&
  4289. ($modified->grade < 0 || $modified->grade === null)) {
  4290. // Different ways to indicate no grade.
  4291. $modified->grade = $current->grade; // Keep existing grade.
  4292. }
  4293. // Treat 0 and null as different values.
  4294. if ($current->grade !== null) {
  4295. $current->grade = floatval($current->grade);
  4296. }
  4297. $gradechanged = $gradecolpresent && $current->grade !== $modified->grade;
  4298. $markingallocationchanged = $this->get_instance()->markingworkflow &&
  4299. $this->get_instance()->markingallocation &&
  4300. ($modified->allocatedmarker !== false) &&
  4301. ($current->allocatedmarker != $modified->allocatedmarker);
  4302. $workflowstatechanged = $this->get_instance()->markingworkflow &&
  4303. ($modified->workflowstate !== false) &&
  4304. ($current->workflowstate != $modified->workflowstate);
  4305. if ($gradechanged || $markingallocationchanged || $workflowstatechanged) {
  4306. // Grade changed.
  4307. if ($this->grading_disabled($modified->userid)) {
  4308. continue;
  4309. }
  4310. if ((int)$current->lastmodified > (int)$modified->lastmodified) {
  4311. // Error - record has been modified since viewing the page.
  4312. return get_string('errorrecordmodified', 'assign');
  4313. } else {
  4314. $modifiedusers[$modified->userid] = $modified;
  4315. }
  4316. }
  4317. }
  4318. $currentgrades->close();
  4319. $adminconfig = $this->get_admin_config();
  4320. $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
  4321. // Ok - ready to process the updates.
  4322. foreach ($modifiedusers as $userid => $modified) {
  4323. $grade = $this->get_user_grade($userid, true);
  4324. $flags = $this->get_user_flags($userid, true);
  4325. $grade->grade= grade_floatval(unformat_float($modified->grade));
  4326. $grade->grader= $USER->id;
  4327. $gradecolpresent = optional_param('quickgrade_' . $userid, false, PARAM_INT) !== false;
  4328. // Save plugins data.
  4329. foreach ($this->feedbackplugins as $plugin) {
  4330. if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
  4331. $plugin->save_quickgrading_changes($userid, $grade);
  4332. if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
  4333. // This is the feedback plugin chose to push comments to the gradebook.
  4334. $grade->feedbacktext = $plugin->text_for_gradebook($grade);
  4335. $grade->feedbackformat = $plugin->format_for_gradebook($grade);
  4336. }
  4337. }
  4338. }
  4339. // These will be set to false if they are not present in the quickgrading
  4340. // form (e.g. column hidden).
  4341. $workflowstatemodified = ($modified->workflowstate !== false) &&
  4342. ($flags->workflowstate != $modified->workflowstate);
  4343. $allocatedmarkermodified = ($modified->allocatedmarker !== false) &&
  4344. ($flags->allocatedmarker != $modified->allocatedmarker);
  4345. if ($workflowstatemodified) {
  4346. $flags->workflowstate = $modified->workflowstate;
  4347. }
  4348. if ($allocatedmarkermodified) {
  4349. $flags->allocatedmarker = $modified->allocatedmarker;
  4350. }
  4351. if ($workflowstatemodified || $allocatedmarkermodified) {
  4352. $this->update_user_flags($flags);
  4353. }
  4354. $this->update_grade($grade);
  4355. // If the conditions are met, allow another attempt.
  4356. $submission = null;
  4357. if ($this->get_instance()->teamsubmission) {
  4358. $submission = $this->get_group_submission($userid, 0, false, -1);
  4359. } else {
  4360. $submission = $this->get_user_submission($userid, false, -1);
  4361. }
  4362. $this->reopen_submission_if_required($userid,
  4363. $submission,
  4364. false);
  4365. // Allow teachers to skip sending notifications.
  4366. if (optional_param('sendstudentnotifications', true, PARAM_BOOL)) {
  4367. $this->notify_grade_modified($grade);
  4368. }
  4369. // Save outcomes.
  4370. if ($CFG->enableoutcomes) {
  4371. $data = array();
  4372. foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
  4373. $oldoutcome = $outcome->grades[$modified->userid]->grade;
  4374. $paramname = 'outcome_' . $outcomeid . '_' . $modified->userid;
  4375. // This will be false if the input was not in the quickgrading
  4376. // form (e.g. column hidden).
  4377. $newoutcome = optional_param($paramname, false, PARAM_INT);
  4378. if ($newoutcome !== false && ($oldoutcome != $newoutcome)) {
  4379. $data[$outcomeid] = $newoutcome;
  4380. }
  4381. }
  4382. if (count($data) > 0) {
  4383. grade_update_outcomes('mod/assign',
  4384. $this->course->id,
  4385. 'mod',
  4386. 'assign',
  4387. $this->get_instance()->id,
  4388. $userid,
  4389. $data);
  4390. }
  4391. }
  4392. $addtolog = $this->add_to_log('grade submission', $this->format_grade_for_log($grade), '', true);
  4393. $params = array(
  4394. 'context' => $this->context,
  4395. 'objectid' => $grade->id,
  4396. 'relateduserid' => $userid
  4397. );
  4398. $event = \mod_assign\event\submission_graded::create($params);
  4399. $event->set_legacy_logdata($addtolog);
  4400. $event->trigger();
  4401. }
  4402. return get_string('quickgradingchangessaved', 'assign');
  4403. }
  4404. /**
  4405. * Reveal student identities to markers (and the gradebook).
  4406. *
  4407. * @return void
  4408. */
  4409. public function reveal_identities() {
  4410. global $DB;
  4411. require_capability('mod/assign:revealidentities', $this->context);
  4412. if ($this->get_instance()->revealidentities || empty($this->get_instance()->blindmarking)) {
  4413. return false;
  4414. }
  4415. // Update the assignment record.
  4416. $update = new stdClass();
  4417. $update->id = $this->get_instance()->id;
  4418. $update->revealidentities = 1;
  4419. $DB->update_record('assign', $update);
  4420. // Refresh the instance data.
  4421. $this->instance = null;
  4422. // Release the grades to the gradebook.
  4423. // First create the column in the gradebook.
  4424. $this->update_gradebook(false, $this->get_course_module()->id);
  4425. // Now release all grades.
  4426. $adminconfig = $this->get_admin_config();
  4427. $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
  4428. $grades = $DB->get_records('assign_grades', array('assignment'=>$this->get_instance()->id));
  4429. $plugin = $this->get_feedback_plugin_by_type($gradebookplugin);
  4430. foreach ($grades as $grade) {
  4431. // Fetch any comments for this student.
  4432. if ($plugin && $plugin->is_enabled() && $plugin->is_visible()) {
  4433. $grade->feedbacktext = $plugin->text_for_gradebook($grade);
  4434. $grade->feedbackformat = $plugin->format_for_gradebook($grade);
  4435. }
  4436. $this->gradebook_item_update(null, $grade);
  4437. }
  4438. $addtolog = $this->add_to_log('reveal identities', get_string('revealidentities', 'assign'), '', true);
  4439. $params = array(
  4440. 'context' => $this->context,
  4441. 'objectid' => $update->id
  4442. );
  4443. $event = \mod_assign\event\identities_revealed::create($params);
  4444. $event->set_legacy_logdata($addtolog);
  4445. $event->trigger();
  4446. }
  4447. /**
  4448. * Reveal student identities to markers (and the gradebook).
  4449. *
  4450. * @return void
  4451. */
  4452. protected function process_reveal_identities() {
  4453. if (!confirm_sesskey()) {
  4454. return false;
  4455. }
  4456. return $this->reveal_identities();
  4457. }
  4458. /**
  4459. * Save grading options.
  4460. *
  4461. * @return void
  4462. */
  4463. protected function process_save_grading_options() {
  4464. global $USER, $CFG;
  4465. // Include grading options form.
  4466. require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
  4467. // Need submit permission to submit an assignment.
  4468. require_capability('mod/assign:grade', $this->context);
  4469. require_sesskey();
  4470. // Is advanced grading enabled?
  4471. $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
  4472. $controller = $gradingmanager->get_active_controller();
  4473. $showquickgrading = empty($controller);
  4474. if (!is_null($this->context)) {
  4475. $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
  4476. } else {
  4477. $showonlyactiveenrolopt = false;
  4478. }
  4479. $markingallocation = $this->get_instance()->markingworkflow &&
  4480. $this->get_instance()->markingallocation &&
  4481. has_capability('mod/assign:manageallocations', $this->context);
  4482. // Get markers to use in drop lists.
  4483. $markingallocationoptions = array();
  4484. if ($markingallocation) {
  4485. $markingallocationoptions[''] = get_string('filternone', 'assign');
  4486. $markers = get_users_by_capability($this->context, 'mod/assign:grade');
  4487. foreach ($markers as $marker) {
  4488. $markingallocationoptions[$marker->id] = fullname($marker);
  4489. }
  4490. }
  4491. // Get marking states to show in form.
  4492. $markingworkflowoptions = array();
  4493. if ($this->get_instance()->markingworkflow) {
  4494. $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
  4495. $markingworkflowoptions[''] = get_string('filternone', 'assign');
  4496. $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notmarked;
  4497. $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user());
  4498. }
  4499. $gradingoptionsparams = array('cm'=>$this->get_course_module()->id,
  4500. 'contextid'=>$this->context->id,
  4501. 'userid'=>$USER->id,
  4502. 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
  4503. 'showquickgrading'=>$showquickgrading,
  4504. 'quickgrading'=>false,
  4505. 'markingworkflowopt' => $markingworkflowoptions,
  4506. 'markingallocationopt' => $markingallocationoptions,
  4507. 'showonlyactiveenrolopt'=>$showonlyactiveenrolopt,
  4508. 'showonlyactiveenrol'=>$this->show_only_active_users());
  4509. $mform = new mod_assign_grading_options_form(null, $gradingoptionsparams);
  4510. if ($formdata = $mform->get_data()) {
  4511. set_user_preference('assign_perpage', $formdata->perpage);
  4512. if (isset($formdata->filter)) {
  4513. set_user_preference('assign_filter', $formdata->filter);
  4514. }
  4515. if (isset($formdata->markerfilter)) {
  4516. set_user_preference('assign_markerfilter', $formdata->markerfilter);
  4517. }
  4518. if (isset($formdata->workflowfilter)) {
  4519. set_user_preference('assign_workflowfilter', $formdata->workflowfilter);
  4520. }
  4521. if ($showquickgrading) {
  4522. set_user_preference('assign_quickgrading', isset($formdata->quickgrading));
  4523. }
  4524. if (!empty($showonlyactiveenrolopt)) {
  4525. $showonlyactiveenrol = isset($formdata->showonlyactiveenrol);
  4526. set_user_preference('grade_report_showonlyactiveenrol', $showonlyactiveenrol);
  4527. $this->showonlyactiveenrol = $showonlyactiveenrol;
  4528. }
  4529. }
  4530. }
  4531. /**
  4532. * Take a grade object and print a short summary for the log file.
  4533. * The size limit for the log file is 255 characters, so be careful not
  4534. * to include too much information.
  4535. *
  4536. * @param stdClass $grade
  4537. * @return string
  4538. */
  4539. public function format_grade_for_log(stdClass $grade) {
  4540. global $DB;
  4541. $user = $DB->get_record('user', array('id' => $grade->userid), '*', MUST_EXIST);
  4542. $info = get_string('gradestudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
  4543. if ($grade->grade != '') {
  4544. $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade, false) . '. ';
  4545. } else {
  4546. $info .= get_string('nograde', 'assign');
  4547. }
  4548. return $info;
  4549. }
  4550. /**
  4551. * Take a submission object and print a short summary for the log file.
  4552. * The size limit for the log file is 255 characters, so be careful not
  4553. * to include too much information.
  4554. *
  4555. * @param stdClass $submission
  4556. * @return string
  4557. */
  4558. protected function format_submission_for_log(stdClass $submission) {
  4559. $info = '';
  4560. $info .= get_string('submissionstatus', 'assign') .
  4561. ': ' .
  4562. get_string('submissionstatus_' . $submission->status, 'assign') .
  4563. '. <br>';
  4564. foreach ($this->submissionplugins as $plugin) {
  4565. if ($plugin->is_enabled() && $plugin->is_visible()) {
  4566. $info .= '<br>' . $plugin->format_for_log($submission);
  4567. }
  4568. }
  4569. return $info;
  4570. }
  4571. /**
  4572. * Require a valid sess key and then call copy_previous_attempt.
  4573. *
  4574. * @param array $notices Any error messages that should be shown
  4575. * to the user at the top of the edit submission form.
  4576. * @return bool
  4577. */
  4578. protected function process_copy_previous_attempt(&$notices) {
  4579. require_sesskey();
  4580. return $this->copy_previous_attempt($notices);
  4581. }
  4582. /**
  4583. * Copy the current assignment submission from the last submitted attempt.
  4584. *
  4585. * @param array $notices Any error messages that should be shown
  4586. * to the user at the top of the edit submission form.
  4587. * @return bool
  4588. */
  4589. public function copy_previous_attempt(&$notices) {
  4590. global $USER, $CFG;
  4591. require_capability('mod/assign:submit', $this->context);
  4592. $instance = $this->get_instance();
  4593. if ($instance->teamsubmission) {
  4594. $submission = $this->get_group_submission($USER->id, 0, true);
  4595. } else {
  4596. $submission = $this->get_user_submission($USER->id, true);
  4597. }
  4598. if (!$submission || $submission->status != ASSIGN_SUBMISSION_STATUS_REOPENED) {
  4599. $notices[] = get_string('submissionnotcopiedinvalidstatus', 'assign');
  4600. return false;
  4601. }
  4602. $flags = $this->get_user_flags($USER->id, false);
  4603. // Get the flags to check if it is locked.
  4604. if ($flags && $flags->locked) {
  4605. $notices[] = get_string('submissionslocked', 'assign');
  4606. return false;
  4607. }
  4608. if ($instance->submissiondrafts) {
  4609. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  4610. } else {
  4611. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  4612. }
  4613. $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
  4614. // Find the previous submission.
  4615. if ($instance->teamsubmission) {
  4616. $previoussubmission = $this->get_group_submission($USER->id, 0, true, $submission->attemptnumber - 1);
  4617. } else {
  4618. $previoussubmission = $this->get_user_submission($USER->id, true, $submission->attemptnumber - 1);
  4619. }
  4620. if (!$previoussubmission) {
  4621. // There was no previous submission so there is nothing else to do.
  4622. return true;
  4623. }
  4624. $pluginerror = false;
  4625. foreach ($this->get_submission_plugins() as $plugin) {
  4626. if ($plugin->is_visible() && $plugin->is_enabled()) {
  4627. if (!$plugin->copy_submission($previoussubmission, $submission)) {
  4628. $notices[] = $plugin->get_error();
  4629. $pluginerror = true;
  4630. }
  4631. }
  4632. }
  4633. if ($pluginerror) {
  4634. return false;
  4635. }
  4636. $addtolog = $this->add_to_log('submissioncopied', $this->format_submission_for_log($submission), '', true);
  4637. $params = array(
  4638. 'context' => $this->context,
  4639. 'objectid' => $submission->id
  4640. );
  4641. $event = \mod_assign\event\submission_duplicated::create($params);
  4642. $event->set_legacy_logdata($addtolog);
  4643. $event->trigger();
  4644. $complete = COMPLETION_INCOMPLETE;
  4645. if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  4646. $complete = COMPLETION_COMPLETE;
  4647. }
  4648. $completion = new completion_info($this->get_course());
  4649. if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
  4650. $completion->update_state($this->get_course_module(), $complete, $USER->id);
  4651. }
  4652. if (!$instance->submissiondrafts) {
  4653. // There is a case for not notifying the student about the submission copy,
  4654. // but it provides a record of the event and if they then cancel editing it
  4655. // is clear that the submission was copied.
  4656. $this->notify_student_submission_copied($submission);
  4657. $this->notify_graders($submission);
  4658. // Trigger assessable_submitted event on submission.
  4659. // The same logic applies here - we could not notify teachers,
  4660. // but then they would wonder why there are submitted assignments
  4661. // and they haven't been notified.
  4662. $params = array(
  4663. 'context' => context_module::instance($this->get_course_module()->id),
  4664. 'objectid' => $submission->id,
  4665. 'other' => array(
  4666. 'submission_editable' => true
  4667. )
  4668. );
  4669. $event = \mod_assign\event\assessable_submitted::create($params);
  4670. $event->trigger();
  4671. }
  4672. return true;
  4673. }
  4674. /**
  4675. * Determine if the current submission is empty or not.
  4676. *
  4677. * @param submission $submission the students submission record to check.
  4678. * @return bool
  4679. */
  4680. public function submission_empty($submission) {
  4681. $allempty = true;
  4682. foreach ($this->submissionplugins as $plugin) {
  4683. if ($plugin->is_enabled() && $plugin->is_visible()) {
  4684. if (!$allempty || !$plugin->is_empty($submission)) {
  4685. $allempty = false;
  4686. }
  4687. }
  4688. }
  4689. return $allempty;
  4690. }
  4691. /**
  4692. * Save assignment submission for the current user.
  4693. *
  4694. * @param stdClass $data
  4695. * @param array $notices Any error messages that should be shown
  4696. * to the user.
  4697. * @return bool
  4698. */
  4699. public function save_submission(stdClass $data, & $notices) {
  4700. global $CFG, $USER;
  4701. require_capability('mod/assign:submit', $this->context);
  4702. $instance = $this->get_instance();
  4703. if ($instance->teamsubmission) {
  4704. $submission = $this->get_group_submission($USER->id, 0, true);
  4705. } else {
  4706. $submission = $this->get_user_submission($USER->id, true);
  4707. }
  4708. if ($instance->submissiondrafts) {
  4709. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  4710. } else {
  4711. $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
  4712. }
  4713. $flags = $this->get_user_flags($USER->id, false);
  4714. // Get the flags to check if it is locked.
  4715. if ($flags && $flags->locked) {
  4716. print_error('submissionslocked', 'assign');
  4717. return true;
  4718. }
  4719. $pluginerror = false;
  4720. foreach ($this->submissionplugins as $plugin) {
  4721. if ($plugin->is_enabled() && $plugin->is_visible()) {
  4722. if (!$plugin->save($submission, $data)) {
  4723. $notices[] = $plugin->get_error();
  4724. $pluginerror = true;
  4725. }
  4726. }
  4727. }
  4728. $allempty = $this->submission_empty($submission);
  4729. if ($pluginerror || $allempty) {
  4730. if ($allempty) {
  4731. $notices[] = get_string('submissionempty', 'mod_assign');
  4732. }
  4733. return false;
  4734. }
  4735. $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
  4736. // Logging.
  4737. if (isset($data->submissionstatement)) {
  4738. $logmessage = get_string('submissionstatementacceptedlog',
  4739. 'mod_assign',
  4740. fullname($USER));
  4741. $this->add_to_log('submission statement accepted', $logmessage);
  4742. $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
  4743. $params = array(
  4744. 'context' => $this->context,
  4745. 'objectid' => $submission->id
  4746. );
  4747. $event = \mod_assign\event\statement_accepted::create($params);
  4748. $event->set_legacy_logdata($addtolog);
  4749. $event->trigger();
  4750. }
  4751. $addtolog = $this->add_to_log('submit', $this->format_submission_for_log($submission), '', true);
  4752. $params = array(
  4753. 'context' => $this->context,
  4754. 'objectid' => $submission->id
  4755. );
  4756. $event = \mod_assign\event\submission_updated::create($params);
  4757. $event->set_legacy_logdata($addtolog);
  4758. $event->trigger();
  4759. $complete = COMPLETION_INCOMPLETE;
  4760. if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
  4761. $complete = COMPLETION_COMPLETE;
  4762. }
  4763. $completion = new completion_info($this->get_course());
  4764. if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
  4765. $completion->update_state($this->get_course_module(), $complete, $USER->id);
  4766. }
  4767. if (!$instance->submissiondrafts) {
  4768. $this->notify_student_submission_receipt($submission);
  4769. $this->notify_graders($submission);
  4770. // Trigger assessable_submitted event on submission.
  4771. $params = array(
  4772. 'context' => context_module::instance($this->get_course_module()->id),
  4773. 'objectid' => $submission->id,
  4774. 'other' => array(
  4775. 'submission_editable' => true
  4776. )
  4777. );
  4778. $event = \mod_assign\event\assessable_submitted::create($params);
  4779. $event->trigger();
  4780. }
  4781. return true;
  4782. }
  4783. /**
  4784. * Save assignment submission.
  4785. *
  4786. * @param moodleform $mform
  4787. * @param array $notices Any error messages that should be shown
  4788. * to the user at the top of the edit submission form.
  4789. * @return bool
  4790. */
  4791. protected function process_save_submission(&$mform, &$notices) {
  4792. global $CFG;
  4793. // Include submission form.
  4794. require_once($CFG->dirroot . '/mod/assign/submission_form.php');
  4795. // Need submit permission to submit an assignment.
  4796. require_sesskey();
  4797. if (!$this->submissions_open()) {
  4798. $notices[] = get_string('duedatereached', 'assign');
  4799. return false;
  4800. }
  4801. $instance = $this->get_instance();
  4802. $data = new stdClass();
  4803. $mform = new mod_assign_submission_form(null, array($this, $data));
  4804. if ($mform->is_cancelled()) {
  4805. return true;
  4806. }
  4807. if ($data = $mform->get_data()) {
  4808. return $this->save_submission($data, $notices);
  4809. }
  4810. return false;
  4811. }
  4812. /**
  4813. * Determine if this users grade can be edited.
  4814. *
  4815. * @param int $userid - The student userid
  4816. * @param bool $checkworkflow - whether to include a check for the workflow state.
  4817. * @return bool $gradingdisabled
  4818. */
  4819. public function grading_disabled($userid, $checkworkflow=true) {
  4820. global $CFG;
  4821. if ($checkworkflow && $this->get_instance()->markingworkflow) {
  4822. $grade = $this->get_user_grade($userid, false);
  4823. $validstates = $this->get_marking_workflow_states_for_current_user();
  4824. if (!empty($grade) && !empty($grade->workflowstate) && !array_key_exists($grade->workflowstate, $validstates)) {
  4825. return true;
  4826. }
  4827. }
  4828. $gradinginfo = grade_get_grades($this->get_course()->id,
  4829. 'mod',
  4830. 'assign',
  4831. $this->get_instance()->id,
  4832. array($userid));
  4833. if (!$gradinginfo) {
  4834. return false;
  4835. }
  4836. if (!isset($gradinginfo->items[0]->grades[$userid])) {
  4837. return false;
  4838. }
  4839. $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked ||
  4840. $gradinginfo->items[0]->grades[$userid]->overridden;
  4841. return $gradingdisabled;
  4842. }
  4843. /**
  4844. * Get an instance of a grading form if advanced grading is enabled.
  4845. * This is specific to the assignment, marker and student.
  4846. *
  4847. * @param int $userid - The student userid
  4848. * @param stdClass|false $grade - The grade record
  4849. * @param bool $gradingdisabled
  4850. * @return mixed gradingform_instance|null $gradinginstance
  4851. */
  4852. protected function get_grading_instance($userid, $grade, $gradingdisabled) {
  4853. global $CFG, $USER;
  4854. $grademenu = make_grades_menu($this->get_instance()->grade);
  4855. $allowgradedecimals = $this->get_instance()->grade > 0;
  4856. $advancedgradingwarning = false;
  4857. $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
  4858. $gradinginstance = null;
  4859. if ($gradingmethod = $gradingmanager->get_active_method()) {
  4860. $controller = $gradingmanager->get_controller($gradingmethod);
  4861. if ($controller->is_form_available()) {
  4862. $itemid = null;
  4863. if ($grade) {
  4864. $itemid = $grade->id;
  4865. }
  4866. if ($gradingdisabled && $itemid) {
  4867. $gradinginstance = $controller->get_current_instance($USER->id, $itemid);
  4868. } else if (!$gradingdisabled) {
  4869. $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
  4870. $gradinginstance = $controller->get_or_create_instance($instanceid,
  4871. $USER->id,
  4872. $itemid);
  4873. }
  4874. } else {
  4875. $advancedgradingwarning = $controller->form_unavailable_notification();
  4876. }
  4877. }
  4878. if ($gradinginstance) {
  4879. $gradinginstance->get_controller()->set_grade_range($grademenu, $allowgradedecimals);
  4880. }
  4881. return $gradinginstance;
  4882. }
  4883. /**
  4884. * Add elements to grade form.
  4885. *
  4886. * @param MoodleQuickForm $mform
  4887. * @param stdClass $data
  4888. * @param array $params
  4889. * @return void
  4890. */
  4891. public function add_grade_form_elements(MoodleQuickForm $mform, stdClass $data, $params) {
  4892. global $USER, $CFG;
  4893. $settings = $this->get_instance();
  4894. $rownum = $params['rownum'];
  4895. $last = $params['last'];
  4896. $useridlistid = $params['useridlistid'];
  4897. $userid = $params['userid'];
  4898. $attemptnumber = $params['attemptnumber'];
  4899. if (!$userid) {
  4900. $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
  4901. if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
  4902. $useridlist = $this->get_grading_userid_list();
  4903. $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
  4904. }
  4905. } else {
  4906. $useridlist = array($userid);
  4907. $rownum = 0;
  4908. $useridlistid = '';
  4909. }
  4910. $userid = $useridlist[$rownum];
  4911. $grade = $this->get_user_grade($userid, false, $attemptnumber);
  4912. $submission = null;
  4913. if ($this->get_instance()->teamsubmission) {
  4914. $submission = $this->get_group_submission($userid, 0, false, $attemptnumber);
  4915. } else {
  4916. $submission = $this->get_user_submission($userid, false, $attemptnumber);
  4917. }
  4918. // Add advanced grading.
  4919. $gradingdisabled = $this->grading_disabled($userid);
  4920. $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled);
  4921. $mform->addElement('header', 'gradeheader', get_string('grade'));
  4922. if ($gradinginstance) {
  4923. $gradingelement = $mform->addElement('grading',
  4924. 'advancedgrading',
  4925. get_string('grade').':',
  4926. array('gradinginstance' => $gradinginstance));
  4927. if ($gradingdisabled) {
  4928. $gradingelement->freeze();
  4929. } else {
  4930. $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
  4931. $mform->setType('advancedgradinginstanceid', PARAM_INT);
  4932. }
  4933. } else {
  4934. // Use simple direct grading.
  4935. if ($this->get_instance()->grade > 0) {
  4936. $name = get_string('gradeoutof', 'assign', $this->get_instance()->grade);
  4937. if (!$gradingdisabled) {
  4938. $gradingelement = $mform->addElement('text', 'grade', $name);
  4939. $mform->addHelpButton('grade', 'gradeoutofhelp', 'assign');
  4940. $mform->setType('grade', PARAM_RAW);
  4941. } else {
  4942. $mform->addElement('hidden', 'grade', $name);
  4943. $mform->hardFreeze('grade');
  4944. $mform->setType('grade', PARAM_RAW);
  4945. $strgradelocked = get_string('gradelocked', 'assign');
  4946. $mform->addElement('static', 'gradedisabled', $name, $strgradelocked);
  4947. $mform->addHelpButton('gradedisabled', 'gradeoutofhelp', 'assign');
  4948. }
  4949. } else {
  4950. $grademenu = array(-1 => get_string("nograde")) + make_grades_menu($this->get_instance()->grade);
  4951. if (count($grademenu) > 1) {
  4952. $gradingelement = $mform->addElement('select', 'grade', get_string('grade') . ':', $grademenu);
  4953. // The grade is already formatted with format_float so it needs to be converted back to an integer.
  4954. if (!empty($data->grade)) {
  4955. $data->grade = (int)unformat_float($data->grade);
  4956. }
  4957. $mform->setType('grade', PARAM_INT);
  4958. if ($gradingdisabled) {
  4959. $gradingelement->freeze();
  4960. }
  4961. }
  4962. }
  4963. }
  4964. $gradinginfo = grade_get_grades($this->get_course()->id,
  4965. 'mod',
  4966. 'assign',
  4967. $this->get_instance()->id,
  4968. $userid);
  4969. if (!empty($CFG->enableoutcomes)) {
  4970. foreach ($gradinginfo->outcomes as $index => $outcome) {
  4971. $options = make_grades_menu(-$outcome->scaleid);
  4972. if ($outcome->grades[$userid]->locked) {
  4973. $options[0] = get_string('nooutcome', 'grades');
  4974. $mform->addElement('static',
  4975. 'outcome_' . $index . '[' . $userid . ']',
  4976. $outcome->name . ':',
  4977. $options[$outcome->grades[$userid]->grade]);
  4978. } else {
  4979. $options[''] = get_string('nooutcome', 'grades');
  4980. $attributes = array('id' => 'menuoutcome_' . $index );
  4981. $mform->addElement('select',
  4982. 'outcome_' . $index . '[' . $userid . ']',
  4983. $outcome->name.':',
  4984. $options,
  4985. $attributes);
  4986. $mform->setType('outcome_' . $index . '[' . $userid . ']', PARAM_INT);
  4987. $mform->setDefault('outcome_' . $index . '[' . $userid . ']',
  4988. $outcome->grades[$userid]->grade);
  4989. }
  4990. }
  4991. }
  4992. $capabilitylist = array('gradereport/grader:view', 'moodle/grade:viewall');
  4993. if (has_all_capabilities($capabilitylist, $this->get_course_context())) {
  4994. $urlparams = array('id'=>$this->get_course()->id);
  4995. $url = new moodle_url('/grade/report/grader/index.php', $urlparams);
  4996. $usergrade = '-';
  4997. if (isset($gradinginfo->items[0]->grades[$userid]->str_grade)) {
  4998. $usergrade = $gradinginfo->items[0]->grades[$userid]->str_grade;
  4999. }
  5000. $gradestring = $this->get_renderer()->action_link($url, $usergrade);
  5001. } else {
  5002. $usergrade = '-';
  5003. if (isset($gradinginfo->items[0]->grades[$userid]) &&
  5004. !$gradinginfo->items[0]->grades[$userid]->hidden) {
  5005. $usergrade = $gradinginfo->items[0]->grades[$userid]->str_grade;
  5006. }
  5007. $gradestring = $usergrade;
  5008. }
  5009. if ($this->get_instance()->markingworkflow) {
  5010. $states = $this->get_marking_workflow_states_for_current_user();
  5011. $options = array('' => get_string('markingworkflowstatenotmarked', 'assign')) + $states;
  5012. $mform->addElement('select', 'workflowstate', get_string('markingworkflowstate', 'assign'), $options);
  5013. $mform->addHelpButton('workflowstate', 'markingworkflowstate', 'assign');
  5014. }
  5015. if ($this->get_instance()->markingworkflow &&
  5016. $this->get_instance()->markingallocation &&
  5017. has_capability('mod/assign:manageallocations', $this->context)) {
  5018. $markers = get_users_by_capability($this->context, 'mod/assign:grade');
  5019. $markerlist = array('' => get_string('choosemarker', 'assign'));
  5020. foreach ($markers as $marker) {
  5021. $markerlist[$marker->id] = fullname($marker);
  5022. }
  5023. $mform->addElement('select', 'allocatedmarker', get_string('allocatedmarker', 'assign'), $markerlist);
  5024. $mform->addHelpButton('allocatedmarker', 'allocatedmarker', 'assign');
  5025. $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW);
  5026. $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW);
  5027. $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE);
  5028. $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED);
  5029. }
  5030. $mform->addElement('static', 'currentgrade', get_string('currentgrade', 'assign'), $gradestring);
  5031. if (count($useridlist) > 1) {
  5032. $strparams = array('current'=>$rownum+1, 'total'=>count($useridlist));
  5033. $name = get_string('outof', 'assign', $strparams);
  5034. $mform->addElement('static', 'gradingstudent', get_string('gradingstudent', 'assign'), $name);
  5035. }
  5036. // Let feedback plugins add elements to the grading form.
  5037. $this->add_plugin_grade_elements($grade, $mform, $data, $userid);
  5038. // Hidden params.
  5039. $mform->addElement('hidden', 'id', $this->get_course_module()->id);
  5040. $mform->setType('id', PARAM_INT);
  5041. $mform->addElement('hidden', 'rownum', $rownum);
  5042. $mform->setType('rownum', PARAM_INT);
  5043. $mform->setConstant('rownum', $rownum);
  5044. $mform->addElement('hidden', 'useridlistid', $useridlistid);
  5045. $mform->setType('useridlistid', PARAM_INT);
  5046. $mform->addElement('hidden', 'attemptnumber', $attemptnumber);
  5047. $mform->setType('attemptnumber', PARAM_INT);
  5048. $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
  5049. $mform->setType('ajax', PARAM_INT);
  5050. if ($this->get_instance()->teamsubmission) {
  5051. $mform->addElement('header', 'groupsubmissionsettings', get_string('groupsubmissionsettings', 'assign'));
  5052. $mform->addElement('selectyesno', 'applytoall', get_string('applytoteam', 'assign'));
  5053. $mform->setDefault('applytoall', 1);
  5054. }
  5055. // Do not show if we are editing a previous attempt.
  5056. if ($attemptnumber == -1 && $this->get_instance()->attemptreopenmethod != ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
  5057. $mform->addElement('header', 'attemptsettings', get_string('attemptsettings', 'assign'));
  5058. $attemptreopenmethod = get_string('attemptreopenmethod_' . $this->get_instance()->attemptreopenmethod, 'assign');
  5059. $mform->addElement('static', 'attemptreopenmethod', get_string('attemptreopenmethod', 'assign'), $attemptreopenmethod);
  5060. $attemptnumber = 0;
  5061. if ($submission) {
  5062. $attemptnumber = $submission->attemptnumber;
  5063. }
  5064. $maxattempts = $this->get_instance()->maxattempts;
  5065. if ($maxattempts == ASSIGN_UNLIMITED_ATTEMPTS) {
  5066. $maxattempts = get_string('unlimitedattempts', 'assign');
  5067. }
  5068. $mform->addelement('static', 'maxattemptslabel', get_string('maxattempts', 'assign'), $maxattempts);
  5069. $mform->addelement('static', 'attemptnumberlabel', get_string('attemptnumber', 'assign'), $attemptnumber + 1);
  5070. $ismanual = $this->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL;
  5071. $issubmission = !empty($submission);
  5072. $isunlimited = $this->get_instance()->maxattempts == ASSIGN_UNLIMITED_ATTEMPTS;
  5073. $islessthanmaxattempts = $issubmission && ($submission->attemptnumber < ($this->get_instance()->maxattempts-1));
  5074. if ($ismanual && (!$issubmission || $isunlimited || $islessthanmaxattempts)) {
  5075. $mform->addElement('selectyesno', 'addattempt', get_string('addattempt', 'assign'));
  5076. $mform->setDefault('addattempt', 0);
  5077. }
  5078. }
  5079. $mform->addElement('selectyesno', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
  5080. // Get assignment visibility information for student.
  5081. $modinfo = get_fast_modinfo($settings->course, $userid);
  5082. $cm = $modinfo->get_cm($this->get_course_module()->id);
  5083. // Don't allow notification to be sent if student can't access assignment.
  5084. if (!$cm->uservisible) {
  5085. $mform->setDefault('sendstudentnotifications', 0);
  5086. $mform->freeze('sendstudentnotifications');
  5087. } else {
  5088. $mform->setDefault('sendstudentnotifications', 1);
  5089. }
  5090. $mform->addElement('hidden', 'action', 'submitgrade');
  5091. $mform->setType('action', PARAM_ALPHA);
  5092. $buttonarray=array();
  5093. $name = get_string('savechanges', 'assign');
  5094. $buttonarray[] = $mform->createElement('submit', 'savegrade', $name);
  5095. if (!$last) {
  5096. $name = get_string('savenext', 'assign');
  5097. $buttonarray[] = $mform->createElement('submit', 'saveandshownext', $name);
  5098. }
  5099. $buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
  5100. $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
  5101. $mform->closeHeaderBefore('buttonar');
  5102. $buttonarray=array();
  5103. if ($rownum > 0) {
  5104. $name = get_string('previous', 'assign');
  5105. $buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', $name);
  5106. }
  5107. if (!$last) {
  5108. $name = get_string('nosavebutnext', 'assign');
  5109. $buttonarray[] = $mform->createElement('submit', 'nosaveandnext', $name);
  5110. }
  5111. if (!empty($buttonarray)) {
  5112. $mform->addGroup($buttonarray, 'navar', '', array(' '), false);
  5113. }
  5114. // The grading form does not work well with shortforms.
  5115. $mform->setDisableShortforms();
  5116. }
  5117. /**
  5118. * Add elements in submission plugin form.
  5119. *
  5120. * @param mixed $submission stdClass|null
  5121. * @param MoodleQuickForm $mform
  5122. * @param stdClass $data
  5123. * @param int $userid The current userid (same as $USER->id)
  5124. * @return void
  5125. */
  5126. protected function add_plugin_submission_elements($submission,
  5127. MoodleQuickForm $mform,
  5128. stdClass $data,
  5129. $userid) {
  5130. foreach ($this->submissionplugins as $plugin) {
  5131. if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
  5132. $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
  5133. if (!$plugin->get_form_elements_for_user($submission, $mform, $data, $userid)) {
  5134. $mform->removeElement('header_' . $plugin->get_type());
  5135. }
  5136. }
  5137. }
  5138. }
  5139. /**
  5140. * Check if feedback plugins installed are enabled.
  5141. *
  5142. * @return bool
  5143. */
  5144. public function is_any_feedback_plugin_enabled() {
  5145. if (!isset($this->cache['any_feedback_plugin_enabled'])) {
  5146. $this->cache['any_feedback_plugin_enabled'] = false;
  5147. foreach ($this->feedbackplugins as $plugin) {
  5148. if ($plugin->is_enabled() && $plugin->is_visible()) {
  5149. $this->cache['any_feedback_plugin_enabled'] = true;
  5150. break;
  5151. }
  5152. }
  5153. }
  5154. return $this->cache['any_feedback_plugin_enabled'];
  5155. }
  5156. /**
  5157. * Check if submission plugins installed are enabled.
  5158. *
  5159. * @return bool
  5160. */
  5161. public function is_any_submission_plugin_enabled() {
  5162. if (!isset($this->cache['any_submission_plugin_enabled'])) {
  5163. $this->cache['any_submission_plugin_enabled'] = false;
  5164. foreach ($this->submissionplugins as $plugin) {
  5165. if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
  5166. $this->cache['any_submission_plugin_enabled'] = true;
  5167. break;
  5168. }
  5169. }
  5170. }
  5171. return $this->cache['any_submission_plugin_enabled'];
  5172. }
  5173. /**
  5174. * Add elements to submission form.
  5175. * @param MoodleQuickForm $mform
  5176. * @param stdClass $data
  5177. * @return void
  5178. */
  5179. public function add_submission_form_elements(MoodleQuickForm $mform, stdClass $data) {
  5180. global $USER;
  5181. // Team submissions.
  5182. if ($this->get_instance()->teamsubmission) {
  5183. $submission = $this->get_group_submission($USER->id, 0, false);
  5184. } else {
  5185. $submission = $this->get_user_submission($USER->id, false);
  5186. }
  5187. // Submission statement.
  5188. $adminconfig = $this->get_admin_config();
  5189. $requiresubmissionstatement = $this->get_instance()->requiresubmissionstatement &&
  5190. !empty($adminconfig->submissionstatement);
  5191. $draftsenabled = $this->get_instance()->submissiondrafts;
  5192. if ($requiresubmissionstatement && !$draftsenabled) {
  5193. $submissionstatement = '';
  5194. if (!empty($adminconfig->submissionstatement)) {
  5195. $submissionstatement = $adminconfig->submissionstatement;
  5196. }
  5197. $mform->addElement('checkbox', 'submissionstatement', '', '&nbsp;' . $submissionstatement);
  5198. $mform->addRule('submissionstatement', get_string('required'), 'required', null, 'client');
  5199. }
  5200. $this->add_plugin_submission_elements($submission, $mform, $data, $USER->id);
  5201. // Hidden params.
  5202. $mform->addElement('hidden', 'id', $this->get_course_module()->id);
  5203. $mform->setType('id', PARAM_INT);
  5204. $mform->addElement('hidden', 'action', 'savesubmission');
  5205. $mform->setType('action', PARAM_TEXT);
  5206. }
  5207. /**
  5208. * Revert to draft.
  5209. *
  5210. * @param int $userid
  5211. * @return boolean
  5212. */
  5213. public function revert_to_draft($userid) {
  5214. global $DB, $USER;
  5215. // Need grade permission.
  5216. require_capability('mod/assign:grade', $this->context);
  5217. if ($this->get_instance()->teamsubmission) {
  5218. $submission = $this->get_group_submission($userid, 0, false);
  5219. } else {
  5220. $submission = $this->get_user_submission($userid, false);
  5221. }
  5222. if (!$submission) {
  5223. return false;
  5224. }
  5225. $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
  5226. $this->update_submission($submission, $userid, true, $this->get_instance()->teamsubmission);
  5227. // Give each submission plugin a chance to process the reverting to draft.
  5228. $plugins = $this->get_submission_plugins();
  5229. foreach ($plugins as $plugin) {
  5230. if ($plugin->is_enabled() && $plugin->is_visible()) {
  5231. $plugin->revert_to_draft($submission);
  5232. }
  5233. }
  5234. // Update the modified time on the grade (grader modified).
  5235. $grade = $this->get_user_grade($userid, true);
  5236. $grade->grader = $USER->id;
  5237. $this->update_grade($grade);
  5238. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  5239. $completion = new completion_info($this->get_course());
  5240. if ($completion->is_enabled($this->get_course_module()) &&
  5241. $this->get_instance()->completionsubmit) {
  5242. $completion->update_state($this->get_course_module(), COMPLETION_INCOMPLETE, $userid);
  5243. }
  5244. $logmessage = get_string('reverttodraftforstudent',
  5245. 'assign',
  5246. array('id'=>$user->id, 'fullname'=>fullname($user)));
  5247. $addtolog = $this->add_to_log('revert submission to draft', $logmessage, '', true);
  5248. $params = array(
  5249. 'context' => $this->context,
  5250. 'objectid' => $submission->id,
  5251. 'relateduserid' => ($this->get_instance()->teamsubmission) ? null : $userid,
  5252. 'other' => array(
  5253. 'newstatus' => $submission->status
  5254. )
  5255. );
  5256. $event = \mod_assign\event\submission_status_updated::create($params);
  5257. $event->set_legacy_logdata($addtolog);
  5258. $event->trigger();
  5259. return true;
  5260. }
  5261. /**
  5262. * Revert to draft.
  5263. * Uses url parameter userid if userid not supplied as a parameter.
  5264. *
  5265. * @param int $userid
  5266. * @return boolean
  5267. */
  5268. protected function process_revert_to_draft($userid = 0) {
  5269. require_sesskey();
  5270. if (!$userid) {
  5271. $userid = required_param('userid', PARAM_INT);
  5272. }
  5273. return $this->revert_to_draft($userid);
  5274. }
  5275. /**
  5276. * Prevent student updates to this submission
  5277. *
  5278. * @param int $userid
  5279. * @return bool
  5280. */
  5281. public function lock_submission($userid) {
  5282. global $USER, $DB;
  5283. // Need grade permission.
  5284. require_capability('mod/assign:grade', $this->context);
  5285. // Give each submission plugin a chance to process the locking.
  5286. $plugins = $this->get_submission_plugins();
  5287. $submission = $this->get_user_submission($userid, false);
  5288. $flags = $this->get_user_flags($userid, true);
  5289. $flags->locked = 1;
  5290. $this->update_user_flags($flags);
  5291. foreach ($plugins as $plugin) {
  5292. if ($plugin->is_enabled() && $plugin->is_visible()) {
  5293. $plugin->lock($submission, $flags);
  5294. }
  5295. }
  5296. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  5297. $logmessage = get_string('locksubmissionforstudent',
  5298. 'assign',
  5299. array('id'=>$user->id, 'fullname'=>fullname($user)));
  5300. $addtolog = $this->add_to_log('lock submission', $logmessage, '', true);
  5301. $params = array(
  5302. 'context' => $this->context,
  5303. 'objectid' => $flags->assignment,
  5304. 'relateduserid' => $user->id
  5305. );
  5306. $event = \mod_assign\event\submission_locked::create($params);
  5307. $event->set_legacy_logdata($addtolog);
  5308. $event->trigger();
  5309. return true;
  5310. }
  5311. /**
  5312. * Set the workflow state for multiple users
  5313. *
  5314. * @return void
  5315. */
  5316. protected function process_set_batch_marking_workflow_state() {
  5317. global $CFG, $DB;
  5318. // Include batch marking workflow form.
  5319. require_once($CFG->dirroot . '/mod/assign/batchsetmarkingworkflowstateform.php');
  5320. $formparams = array(
  5321. 'userscount' => 0, // This form is never re-displayed, so we don't need to
  5322. 'usershtml' => '', // initialise these parameters with real information.
  5323. 'markingworkflowstates' => $this->get_marking_workflow_states_for_current_user()
  5324. );
  5325. $mform = new mod_assign_batch_set_marking_workflow_state_form(null, $formparams);
  5326. if ($mform->is_cancelled()) {
  5327. return true;
  5328. }
  5329. if ($formdata = $mform->get_data()) {
  5330. $useridlist = explode(',', $formdata->selectedusers);
  5331. $state = $formdata->markingworkflowstate;
  5332. foreach ($useridlist as $userid) {
  5333. $flags = $this->get_user_flags($userid, true);
  5334. $flags->workflowstate = $state;
  5335. $gradingdisabled = $this->grading_disabled($userid);
  5336. // Will not apply update if user does not have permission to assign this workflow state.
  5337. if (!$gradingdisabled && $this->update_user_flags($flags)) {
  5338. if ($state == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
  5339. // Update Gradebook.
  5340. $assign = clone $this->get_instance();
  5341. $assign->cmidnumber = $this->get_course_module()->idnumber;
  5342. // Set assign gradebook feedback plugin status.
  5343. $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
  5344. assign_update_grades($assign, $userid);
  5345. }
  5346. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  5347. $params = array('id'=>$user->id,
  5348. 'fullname'=>fullname($user),
  5349. 'state'=>$state);
  5350. $message = get_string('setmarkingworkflowstateforlog', 'assign', $params);
  5351. $addtolog = $this->add_to_log('set marking workflow state', $message, '', true);
  5352. $params = array(
  5353. 'context' => $this->context,
  5354. 'objectid' => $this->get_instance()->id,
  5355. 'relateduserid' => $userid,
  5356. 'other' => array(
  5357. 'newstate' => $state
  5358. )
  5359. );
  5360. $event = \mod_assign\event\workflow_state_updated::create($params);
  5361. $event->set_legacy_logdata($addtolog);
  5362. $event->trigger();
  5363. }
  5364. }
  5365. }
  5366. }
  5367. /**
  5368. * Set the marking allocation for multiple users
  5369. *
  5370. * @return void
  5371. */
  5372. protected function process_set_batch_marking_allocation() {
  5373. global $CFG, $DB;
  5374. // Include batch marking allocation form.
  5375. require_once($CFG->dirroot . '/mod/assign/batchsetallocatedmarkerform.php');
  5376. $formparams = array(
  5377. 'userscount' => 0, // This form is never re-displayed, so we don't need to
  5378. 'usershtml' => '' // initialise these parameters with real information.
  5379. );
  5380. $markers = get_users_by_capability($this->get_context(), 'mod/assign:grade');
  5381. $markerlist = array();
  5382. foreach ($markers as $marker) {
  5383. $markerlist[$marker->id] = fullname($marker);
  5384. }
  5385. $formparams['markers'] = $markerlist;
  5386. $mform = new mod_assign_batch_set_allocatedmarker_form(null, $formparams);
  5387. if ($mform->is_cancelled()) {
  5388. return true;
  5389. }
  5390. if ($formdata = $mform->get_data()) {
  5391. $useridlist = explode(',', $formdata->selectedusers);
  5392. $marker = $DB->get_record('user', array('id' => $formdata->allocatedmarker), '*', MUST_EXIST);
  5393. foreach ($useridlist as $userid) {
  5394. $flags = $this->get_user_flags($userid, true);
  5395. if ($flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW ||
  5396. $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW ||
  5397. $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE ||
  5398. $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
  5399. continue; // Allocated marker can only be changed in certain workflow states.
  5400. }
  5401. $flags->allocatedmarker = $marker->id;
  5402. if ($this->update_user_flags($flags)) {
  5403. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  5404. $params = array('id'=>$user->id,
  5405. 'fullname'=>fullname($user),
  5406. 'marker'=>fullname($marker));
  5407. $message = get_string('setmarkerallocationforlog', 'assign', $params);
  5408. $addtolog = $this->add_to_log('set marking allocation', $message, '', true);
  5409. $params = array(
  5410. 'context' => $this->context,
  5411. 'objectid' => $this->get_instance()->id,
  5412. 'relateduserid' => $userid,
  5413. 'other' => array(
  5414. 'markerid' => $marker->id
  5415. )
  5416. );
  5417. $event = \mod_assign\event\marker_updated::create($params);
  5418. $event->set_legacy_logdata($addtolog);
  5419. $event->trigger();
  5420. }
  5421. }
  5422. }
  5423. }
  5424. /**
  5425. * Prevent student updates to this submission.
  5426. * Uses url parameter userid.
  5427. *
  5428. * @param int $userid
  5429. * @return void
  5430. */
  5431. protected function process_lock_submission($userid = 0) {
  5432. require_sesskey();
  5433. if (!$userid) {
  5434. $userid = required_param('userid', PARAM_INT);
  5435. }
  5436. return $this->lock_submission($userid);
  5437. }
  5438. /**
  5439. * Unlock the student submission.
  5440. *
  5441. * @param int $userid
  5442. * @return bool
  5443. */
  5444. public function unlock_submission($userid) {
  5445. global $USER, $DB;
  5446. // Need grade permission.
  5447. require_capability('mod/assign:grade', $this->context);
  5448. // Give each submission plugin a chance to process the unlocking.
  5449. $plugins = $this->get_submission_plugins();
  5450. $submission = $this->get_user_submission($userid, false);
  5451. $flags = $this->get_user_flags($userid, true);
  5452. $flags->locked = 0;
  5453. $this->update_user_flags($flags);
  5454. foreach ($plugins as $plugin) {
  5455. if ($plugin->is_enabled() && $plugin->is_visible()) {
  5456. $plugin->unlock($submission, $flags);
  5457. }
  5458. }
  5459. $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
  5460. $logmessage = get_string('unlocksubmissionforstudent',
  5461. 'assign',
  5462. array('id'=>$user->id, 'fullname'=>fullname($user)));
  5463. $addtolog = $this->add_to_log('unlock submission', $logmessage, '', true);
  5464. $params = array(
  5465. 'context' => $this->context,
  5466. 'objectid' => $flags->assignment,
  5467. 'relateduserid' => $user->id
  5468. );
  5469. $event = \mod_assign\event\submission_unlocked::create($params);
  5470. $event->set_legacy_logdata($addtolog);
  5471. $event->trigger();
  5472. return true;
  5473. }
  5474. /**
  5475. * Unlock the student submission.
  5476. * Uses url parameter userid.
  5477. *
  5478. * @param int $userid
  5479. * @return bool
  5480. */
  5481. protected function process_unlock_submission($userid = 0) {
  5482. require_sesskey();
  5483. if (!$userid) {
  5484. $userid = required_param('userid', PARAM_INT);
  5485. }
  5486. return $this->unlock_submission($userid);
  5487. }
  5488. /**
  5489. * Apply a grade from a grading form to a user (may be called multiple times for a group submission).
  5490. *
  5491. * @param stdClass $formdata - the data from the form
  5492. * @param int $userid - the user to apply the grade to
  5493. * @param int $attemptnumber - The attempt number to apply the grade to.
  5494. * @return void
  5495. */
  5496. protected function apply_grade_to_user($formdata, $userid, $attemptnumber) {
  5497. global $USER, $CFG, $DB;
  5498. $grade = $this->get_user_grade($userid, true, $attemptnumber);
  5499. $gradingdisabled = $this->grading_disabled($userid);
  5500. $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled);
  5501. if (!$gradingdisabled) {
  5502. if ($gradinginstance) {
  5503. $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading,
  5504. $grade->id);
  5505. } else {
  5506. // Handle the case when grade is set to No Grade.
  5507. if (isset($formdata->grade)) {
  5508. $grade->grade = grade_floatval(unformat_float($formdata->grade));
  5509. }
  5510. }
  5511. if (isset($formdata->workflowstate) || isset($formdata->allocatedmarker)) {
  5512. $flags = $this->get_user_flags($userid, true);
  5513. $flags->workflowstate = isset($formdata->workflowstate) ? $formdata->workflowstate : $flags->workflowstate;
  5514. $flags->allocatedmarker = isset($formdata->allocatedmarker) ? $formdata->allocatedmarker : $flags->allocatedmarker;
  5515. $this->update_user_flags($flags);
  5516. }
  5517. }
  5518. $grade->grader= $USER->id;
  5519. $adminconfig = $this->get_admin_config();
  5520. $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
  5521. // Call save in plugins.
  5522. foreach ($this->feedbackplugins as $plugin) {
  5523. if ($plugin->is_enabled() && $plugin->is_visible()) {
  5524. if (!$plugin->save($grade, $formdata)) {
  5525. $result = false;
  5526. print_error($plugin->get_error());
  5527. }
  5528. if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
  5529. // This is the feedback plugin chose to push comments to the gradebook.
  5530. $grade->feedbacktext = $plugin->text_for_gradebook($grade);
  5531. $grade->feedbackformat = $plugin->format_for_gradebook($grade);
  5532. }
  5533. }
  5534. }
  5535. $this->update_grade($grade);
  5536. // Note the default if not provided for this option is true (e.g. webservices).
  5537. // This is for backwards compatibility.
  5538. if (!isset($formdata->sendstudentnotifications) || $formdata->sendstudentnotifications) {
  5539. $this->notify_grade_modified($grade);
  5540. }
  5541. $addtolog = $this->add_to_log('grade submission', $this->format_grade_for_log($grade), '', true);
  5542. $params = array(
  5543. 'context' => $this->context,
  5544. 'objectid' => $grade->id,
  5545. 'relateduserid' => $userid
  5546. );
  5547. $event = \mod_assign\event\submission_graded::create($params);
  5548. $event->set_legacy_logdata($addtolog);
  5549. $event->trigger();
  5550. }
  5551. /**
  5552. * Save outcomes submitted from grading form.
  5553. *
  5554. * @param int $userid
  5555. * @param stdClass $formdata
  5556. * @param int $sourceuserid The user ID under which the outcome data is accessible. This is relevant
  5557. * for an outcome set to a user but applied to an entire group.
  5558. */
  5559. protected function process_outcomes($userid, $formdata, $sourceuserid = null) {
  5560. global $CFG, $USER;
  5561. if (empty($CFG->enableoutcomes)) {
  5562. return;
  5563. }
  5564. if ($this->grading_disabled($userid)) {
  5565. return;
  5566. }
  5567. require_once($CFG->libdir.'/gradelib.php');
  5568. $data = array();
  5569. $gradinginfo = grade_get_grades($this->get_course()->id,
  5570. 'mod',
  5571. 'assign',
  5572. $this->get_instance()->id,
  5573. $userid);
  5574. if (!empty($gradinginfo->outcomes)) {
  5575. foreach ($gradinginfo->outcomes as $index => $oldoutcome) {
  5576. $name = 'outcome_'.$index;
  5577. $sourceuserid = $sourceuserid !== null ? $sourceuserid : $userid;
  5578. if (isset($formdata->{$name}[$sourceuserid]) &&
  5579. $oldoutcome->grades[$userid]->grade != $formdata->{$name}[$sourceuserid]) {
  5580. $data[$index] = $formdata->{$name}[$sourceuserid];
  5581. }
  5582. }
  5583. }
  5584. if (count($data) > 0) {
  5585. grade_update_outcomes('mod/assign',
  5586. $this->course->id,
  5587. 'mod',
  5588. 'assign',
  5589. $this->get_instance()->id,
  5590. $userid,
  5591. $data);
  5592. }
  5593. }
  5594. /**
  5595. * If the requirements are met - reopen the submission for another attempt.
  5596. * Only call this function when grading the latest attempt.
  5597. *
  5598. * @param int $userid The userid.
  5599. * @param stdClass $submission The submission (may be a group submission).
  5600. * @param bool $addattempt - True if the "allow another attempt" checkbox was checked.
  5601. * @return bool - true if another attempt was added.
  5602. */
  5603. protected function reopen_submission_if_required($userid, $submission, $addattempt) {
  5604. $instance = $this->get_instance();
  5605. $maxattemptsreached = !empty($submission) &&
  5606. $submission->attemptnumber >= ($instance->maxattempts - 1) &&
  5607. $instance->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS;
  5608. $shouldreopen = false;
  5609. if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS) {
  5610. // Check the gradetopass from the gradebook.
  5611. $gradinginfo = grade_get_grades($this->get_course()->id,
  5612. 'mod',
  5613. 'assign',
  5614. $instance->id,
  5615. $userid);
  5616. // What do we do if the grade has not been added to the gradebook (e.g. blind marking)?
  5617. $gradingitem = null;
  5618. $gradebookgrade = null;
  5619. if (isset($gradinginfo->items[0])) {
  5620. $gradingitem = $gradinginfo->items[0];
  5621. $gradebookgrade = $gradingitem->grades[$userid];
  5622. }
  5623. if ($gradebookgrade) {
  5624. // TODO: This code should call grade_grade->is_passed().
  5625. $shouldreopen = true;
  5626. if (is_null($gradebookgrade->grade)) {
  5627. $shouldreopen = false;
  5628. }
  5629. if (empty($gradingitem->gradepass) || $gradingitem->gradepass == $gradingitem->grademin) {
  5630. $shouldreopen = false;
  5631. }
  5632. if ($gradebookgrade->grade >= $gradingitem->gradepass) {
  5633. $shouldreopen = false;
  5634. }
  5635. }
  5636. }
  5637. if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL &&
  5638. !empty($addattempt)) {
  5639. $shouldreopen = true;
  5640. }
  5641. if ($shouldreopen && !$maxattemptsreached) {
  5642. $this->add_attempt($userid);
  5643. return true;
  5644. }
  5645. return false;
  5646. }
  5647. /**
  5648. * Save grade update.
  5649. *
  5650. * @param int $userid
  5651. * @param stdClass $data
  5652. * @return bool - was the grade saved
  5653. */
  5654. public function save_grade($userid, $data) {
  5655. // Need grade permission.
  5656. require_capability('mod/assign:grade', $this->context);
  5657. $instance = $this->get_instance();
  5658. $submission = null;
  5659. if ($instance->teamsubmission) {
  5660. $submission = $this->get_group_submission($userid, 0, false, $data->attemptnumber);
  5661. } else {
  5662. $submission = $this->get_user_submission($userid, false, $data->attemptnumber);
  5663. }
  5664. if ($instance->teamsubmission && $data->applytoall) {
  5665. $groupid = 0;
  5666. if ($this->get_submission_group($userid)) {
  5667. $group = $this->get_submission_group($userid);
  5668. if ($group) {
  5669. $groupid = $group->id;
  5670. }
  5671. }
  5672. $members = $this->get_submission_group_members($groupid, true);
  5673. foreach ($members as $member) {
  5674. // User may exist in multple groups (which should put them in the default group).
  5675. $this->apply_grade_to_user($data, $member->id, $data->attemptnumber);
  5676. $this->process_outcomes($member->id, $data, $userid);
  5677. }
  5678. } else {
  5679. $this->apply_grade_to_user($data, $userid, $data->attemptnumber);
  5680. $this->process_outcomes($userid, $data);
  5681. }
  5682. if ($data->attemptnumber == -1) {
  5683. // We only allow another attempt when grading the latest submission.
  5684. $this->reopen_submission_if_required($userid,
  5685. $submission,
  5686. !empty($data->addattempt));
  5687. }
  5688. return true;
  5689. }
  5690. /**
  5691. * Save grade.
  5692. *
  5693. * @param moodleform $mform
  5694. * @return bool - was the grade saved
  5695. */
  5696. protected function process_save_grade(&$mform) {
  5697. global $CFG;
  5698. // Include grade form.
  5699. require_once($CFG->dirroot . '/mod/assign/gradeform.php');
  5700. require_sesskey();
  5701. $instance = $this->get_instance();
  5702. $rownum = required_param('rownum', PARAM_INT);
  5703. $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
  5704. $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
  5705. $userid = optional_param('userid', 0, PARAM_INT);
  5706. $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
  5707. if (!$userid) {
  5708. if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
  5709. $useridlist = $this->get_grading_userid_list();
  5710. $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
  5711. }
  5712. } else {
  5713. $useridlist = array($userid);
  5714. $rownum = 0;
  5715. }
  5716. $last = false;
  5717. $userid = $useridlist[$rownum];
  5718. if ($rownum == count($useridlist) - 1) {
  5719. $last = true;
  5720. }
  5721. $data = new stdClass();
  5722. $gradeformparams = array('rownum'=>$rownum,
  5723. 'useridlistid'=>$useridlistid,
  5724. 'last'=>false,
  5725. 'attemptnumber'=>$attemptnumber,
  5726. 'userid'=>optional_param('userid', 0, PARAM_INT));
  5727. $mform = new mod_assign_grade_form(null,
  5728. array($this, $data, $gradeformparams),
  5729. 'post',
  5730. '',
  5731. array('class'=>'gradeform'));
  5732. if ($formdata = $mform->get_data()) {
  5733. return $this->save_grade($userid, $formdata);
  5734. } else {
  5735. return false;
  5736. }
  5737. }
  5738. /**
  5739. * This function is a static wrapper around can_upgrade.
  5740. *
  5741. * @param string $type The plugin type
  5742. * @param int $version The plugin version
  5743. * @return bool
  5744. */
  5745. public static function can_upgrade_assignment($type, $version) {
  5746. $assignment = new assign(null, null, null);
  5747. return $assignment->can_upgrade($type, $version);
  5748. }
  5749. /**
  5750. * This function returns true if it can upgrade an assignment from the 2.2 module.
  5751. *
  5752. * @param string $type The plugin type
  5753. * @param int $version The plugin version
  5754. * @return bool
  5755. */
  5756. public function can_upgrade($type, $version) {
  5757. if ($type == 'offline' && $version >= 2011112900) {
  5758. return true;
  5759. }
  5760. foreach ($this->submissionplugins as $plugin) {
  5761. if ($plugin->can_upgrade($type, $version)) {
  5762. return true;
  5763. }
  5764. }
  5765. foreach ($this->feedbackplugins as $plugin) {
  5766. if ($plugin->can_upgrade($type, $version)) {
  5767. return true;
  5768. }
  5769. }
  5770. return false;
  5771. }
  5772. /**
  5773. * Copy all the files from the old assignment files area to the new one.
  5774. * This is used by the plugin upgrade code.
  5775. *
  5776. * @param int $oldcontextid The old assignment context id
  5777. * @param int $oldcomponent The old assignment component ('assignment')
  5778. * @param int $oldfilearea The old assignment filearea ('submissions')
  5779. * @param int $olditemid The old submissionid (can be null e.g. intro)
  5780. * @param int $newcontextid The new assignment context id
  5781. * @param int $newcomponent The new assignment component ('assignment')
  5782. * @param int $newfilearea The new assignment filearea ('submissions')
  5783. * @param int $newitemid The new submissionid (can be null e.g. intro)
  5784. * @return int The number of files copied
  5785. */
  5786. public function copy_area_files_for_upgrade($oldcontextid,
  5787. $oldcomponent,
  5788. $oldfilearea,
  5789. $olditemid,
  5790. $newcontextid,
  5791. $newcomponent,
  5792. $newfilearea,
  5793. $newitemid) {
  5794. // Note, this code is based on some code in filestorage - but that code
  5795. // deleted the old files (which we don't want).
  5796. $count = 0;
  5797. $fs = get_file_storage();
  5798. $oldfiles = $fs->get_area_files($oldcontextid,
  5799. $oldcomponent,
  5800. $oldfilearea,
  5801. $olditemid,
  5802. 'id',
  5803. false);
  5804. foreach ($oldfiles as $oldfile) {
  5805. $filerecord = new stdClass();
  5806. $filerecord->contextid = $newcontextid;
  5807. $filerecord->component = $newcomponent;
  5808. $filerecord->filearea = $newfilearea;
  5809. $filerecord->itemid = $newitemid;
  5810. $fs->create_file_from_storedfile($filerecord, $oldfile);
  5811. $count += 1;
  5812. }
  5813. return $count;
  5814. }
  5815. /**
  5816. * Add a new attempt for each user in the list - but reopen each group assignment
  5817. * at most 1 time.
  5818. *
  5819. * @param array $useridlist Array of userids to reopen.
  5820. * @return bool
  5821. */
  5822. protected function process_add_attempt_group($useridlist) {
  5823. $groupsprocessed = array();
  5824. $result = true;
  5825. foreach ($useridlist as $userid) {
  5826. $groupid = 0;
  5827. $group = $this->get_submission_group($userid);
  5828. if ($group) {
  5829. $groupid = $group->id;
  5830. }
  5831. if (empty($groupsprocessed[$groupid])) {
  5832. $result = $this->process_add_attempt($userid) && $result;
  5833. $groupsprocessed[$groupid] = true;
  5834. }
  5835. }
  5836. return $result;
  5837. }
  5838. /**
  5839. * Check for a sess key and then call add_attempt.
  5840. *
  5841. * @param int $userid int The user to add the attempt for
  5842. * @return bool - true if successful.
  5843. */
  5844. protected function process_add_attempt($userid) {
  5845. require_sesskey();
  5846. return $this->add_attempt($userid);
  5847. }
  5848. /**
  5849. * Add a new attempt for a user.
  5850. *
  5851. * @param int $userid int The user to add the attempt for
  5852. * @return bool - true if successful.
  5853. */
  5854. protected function add_attempt($userid) {
  5855. require_capability('mod/assign:grade', $this->context);
  5856. if ($this->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
  5857. return false;
  5858. }
  5859. if ($this->get_instance()->teamsubmission) {
  5860. $submission = $this->get_group_submission($userid, 0, false);
  5861. } else {
  5862. $submission = $this->get_user_submission($userid, false);
  5863. }
  5864. if (!$submission) {
  5865. return false;
  5866. }
  5867. // No more than max attempts allowed.
  5868. if ($this->get_instance()->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS &&
  5869. $submission->attemptnumber >= ($this->get_instance()->maxattempts - 1)) {
  5870. return false;
  5871. }
  5872. // Create the new submission record for the group/user.
  5873. if ($this->get_instance()->teamsubmission) {
  5874. $submission = $this->get_group_submission($userid, 0, true, $submission->attemptnumber+1);
  5875. } else {
  5876. $submission = $this->get_user_submission($userid, true, $submission->attemptnumber+1);
  5877. }
  5878. // Set the status of the new attempt to reopened.
  5879. $submission->status = ASSIGN_SUBMISSION_STATUS_REOPENED;
  5880. $this->update_submission($submission, $userid, false, $this->get_instance()->teamsubmission);
  5881. return true;
  5882. }
  5883. /**
  5884. * Get an upto date list of user grades and feedback for the gradebook.
  5885. *
  5886. * @param int $userid int or 0 for all users
  5887. * @return array of grade data formated for the gradebook api
  5888. * The data required by the gradebook api is userid,
  5889. * rawgrade,
  5890. * feedback,
  5891. * feedbackformat,
  5892. * usermodified,
  5893. * dategraded,
  5894. * datesubmitted
  5895. */
  5896. public function get_user_grades_for_gradebook($userid) {
  5897. global $DB, $CFG;
  5898. $grades = array();
  5899. $assignmentid = $this->get_instance()->id;
  5900. $adminconfig = $this->get_admin_config();
  5901. $gradebookpluginname = $adminconfig->feedback_plugin_for_gradebook;
  5902. $gradebookplugin = null;
  5903. // Find the gradebook plugin.
  5904. foreach ($this->feedbackplugins as $plugin) {
  5905. if ($plugin->is_enabled() && $plugin->is_visible()) {
  5906. if (('assignfeedback_' . $plugin->get_type()) == $gradebookpluginname) {
  5907. $gradebookplugin = $plugin;
  5908. }
  5909. }
  5910. }
  5911. if ($userid) {
  5912. $where = ' WHERE u.id = :userid ';
  5913. } else {
  5914. $where = ' WHERE u.id != :userid ';
  5915. }
  5916. $submissionmaxattempt = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt
  5917. FROM {assign_submission} mxs
  5918. WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid';
  5919. $grademaxattempt = 'SELECT mxg.userid, MAX(mxg.attemptnumber) AS maxattempt
  5920. FROM {assign_grades} mxg
  5921. WHERE mxg.assignment = :assignid2 GROUP BY mxg.userid';
  5922. // When the gradebook asks us for grades - only return the last attempt for each user.
  5923. $params = array('assignid1'=>$assignmentid,
  5924. 'assignid2'=>$assignmentid,
  5925. 'assignid3'=>$assignmentid,
  5926. 'assignid4'=>$assignmentid,
  5927. 'userid'=>$userid);
  5928. $graderesults = $DB->get_recordset_sql('SELECT
  5929. u.id as userid,
  5930. s.timemodified as datesubmitted,
  5931. g.grade as rawgrade,
  5932. g.timemodified as dategraded,
  5933. g.grader as usermodified
  5934. FROM {user} u
  5935. LEFT JOIN ( ' . $submissionmaxattempt . ' ) smx ON u.id = smx.userid
  5936. LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid
  5937. LEFT JOIN {assign_submission} s
  5938. ON u.id = s.userid and s.assignment = :assignid3 AND
  5939. s.attemptnumber = smx.maxattempt
  5940. JOIN {assign_grades} g
  5941. ON u.id = g.userid and g.assignment = :assignid4 AND
  5942. g.attemptnumber = gmx.maxattempt' .
  5943. $where, $params);
  5944. foreach ($graderesults as $result) {
  5945. $gradebookgrade = clone $result;
  5946. // Now get the feedback.
  5947. if ($gradebookplugin) {
  5948. $grade = $this->get_user_grade($result->userid, false);
  5949. if ($grade) {
  5950. $gradebookgrade->feedbacktext = $gradebookplugin->text_for_gradebook($grade);
  5951. $gradebookgrade->feedbackformat = $gradebookplugin->format_for_gradebook($grade);
  5952. }
  5953. }
  5954. $grades[$gradebookgrade->userid] = $gradebookgrade;
  5955. }
  5956. $graderesults->close();
  5957. return $grades;
  5958. }
  5959. /**
  5960. * Call the static version of this function
  5961. *
  5962. * @param int $userid The userid to lookup
  5963. * @return int The unique id
  5964. */
  5965. public function get_uniqueid_for_user($userid) {
  5966. return self::get_uniqueid_for_user_static($this->get_instance()->id, $userid);
  5967. }
  5968. /**
  5969. * Foreach participant in the course - assign them a random id.
  5970. *
  5971. * @param int $assignid The assignid to lookup
  5972. */
  5973. public static function allocate_unique_ids($assignid) {
  5974. global $DB;
  5975. $cm = get_coursemodule_from_instance('assign', $assignid, 0, false, MUST_EXIST);
  5976. $context = context_module::instance($cm->id);
  5977. $currentgroup = groups_get_activity_group($cm, true);
  5978. $users = get_enrolled_users($context, "mod/assign:submit", $currentgroup, 'u.id');
  5979. // Shuffle the users.
  5980. shuffle($users);
  5981. foreach ($users as $user) {
  5982. $record = $DB->get_record('assign_user_mapping',
  5983. array('assignment'=>$assignid, 'userid'=>$user->id),
  5984. 'id');
  5985. if (!$record) {
  5986. $record = new stdClass();
  5987. $record->assignment = $assignid;
  5988. $record->userid = $user->id;
  5989. $DB->insert_record('assign_user_mapping', $record);
  5990. }
  5991. }
  5992. }
  5993. /**
  5994. * Lookup this user id and return the unique id for this assignment.
  5995. *
  5996. * @param int $assignid The assignment id
  5997. * @param int $userid The userid to lookup
  5998. * @return int The unique id
  5999. */
  6000. public static function get_uniqueid_for_user_static($assignid, $userid) {
  6001. global $DB;
  6002. // Search for a record.
  6003. $params = array('assignment'=>$assignid, 'userid'=>$userid);
  6004. if ($record = $DB->get_record('assign_user_mapping', $params, 'id')) {
  6005. return $record->id;
  6006. }
  6007. // Be a little smart about this - there is no record for the current user.
  6008. // We should ensure any unallocated ids for the current participant
  6009. // list are distrubited randomly.
  6010. self::allocate_unique_ids($assignid);
  6011. // Retry the search for a record.
  6012. if ($record = $DB->get_record('assign_user_mapping', $params, 'id')) {
  6013. return $record->id;
  6014. }
  6015. // The requested user must not be a participant. Add a record anyway.
  6016. $record = new stdClass();
  6017. $record->assignment = $assignid;
  6018. $record->userid = $userid;
  6019. return $DB->insert_record('assign_user_mapping', $record);
  6020. }
  6021. /**
  6022. * Call the static version of this function.
  6023. *
  6024. * @param int $uniqueid The uniqueid to lookup
  6025. * @return int The user id or false if they don't exist
  6026. */
  6027. public function get_user_id_for_uniqueid($uniqueid) {
  6028. return self::get_user_id_for_uniqueid_static($this->get_instance()->id, $uniqueid);
  6029. }
  6030. /**
  6031. * Lookup this unique id and return the user id for this assignment.
  6032. *
  6033. * @param int $assignid The id of the assignment this user mapping is in
  6034. * @param int $uniqueid The uniqueid to lookup
  6035. * @return int The user id or false if they don't exist
  6036. */
  6037. public static function get_user_id_for_uniqueid_static($assignid, $uniqueid) {
  6038. global $DB;
  6039. // Search for a record.
  6040. if ($record = $DB->get_record('assign_user_mapping',
  6041. array('assignment'=>$assignid, 'id'=>$uniqueid),
  6042. 'userid',
  6043. IGNORE_MISSING)) {
  6044. return $record->userid;
  6045. }
  6046. return false;
  6047. }
  6048. /**
  6049. * Get the list of marking_workflow states the current user has permission to transition a grade to.
  6050. *
  6051. * @return array of state => description
  6052. */
  6053. public function get_marking_workflow_states_for_current_user() {
  6054. if (!empty($this->markingworkflowstates)) {
  6055. return $this->markingworkflowstates;
  6056. }
  6057. $states = array();
  6058. if (has_capability('mod/assign:grade', $this->context)) {
  6059. $states[ASSIGN_MARKING_WORKFLOW_STATE_INMARKING] = get_string('markingworkflowstateinmarking', 'assign');
  6060. $states[ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW] = get_string('markingworkflowstatereadyforreview', 'assign');
  6061. }
  6062. if (has_any_capability(array('mod/assign:reviewgrades',
  6063. 'mod/assign:managegrades'), $this->context)) {
  6064. $states[ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW] = get_string('markingworkflowstateinreview', 'assign');
  6065. $states[ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE] = get_string('markingworkflowstatereadyforrelease', 'assign');
  6066. }
  6067. if (has_any_capability(array('mod/assign:releasegrades',
  6068. 'mod/assign:managegrades'), $this->context)) {
  6069. $states[ASSIGN_MARKING_WORKFLOW_STATE_RELEASED] = get_string('markingworkflowstatereleased', 'assign');
  6070. }
  6071. $this->markingworkflowstates = $states;
  6072. return $this->markingworkflowstates;
  6073. }
  6074. /**
  6075. * Check is only active users in course should be shown.
  6076. *
  6077. * @return bool true if only active users should be shown.
  6078. */
  6079. public function show_only_active_users() {
  6080. global $CFG;
  6081. if (is_null($this->showonlyactiveenrol)) {
  6082. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
  6083. $this->showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
  6084. if (!is_null($this->context)) {
  6085. $this->showonlyactiveenrol = $this->showonlyactiveenrol ||
  6086. !has_capability('moodle/course:viewsuspendedusers', $this->context);
  6087. }
  6088. }
  6089. return $this->showonlyactiveenrol;
  6090. }
  6091. /**
  6092. * Return true is user is active user in course else false
  6093. *
  6094. * @param int $userid
  6095. * @return bool true is user is active in course.
  6096. */
  6097. public function is_active_user($userid) {
  6098. return !in_array($userid, get_suspended_userids($this->context, true));
  6099. }
  6100. }
  6101. /**
  6102. * Portfolio caller class for mod_assign.
  6103. *
  6104. * @package mod_assign
  6105. * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  6106. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  6107. */
  6108. class assign_portfolio_caller extends portfolio_module_caller_base {
  6109. /** @var int callback arg - the id of submission we export */
  6110. protected $sid;
  6111. /** @var string component of the submission files we export*/
  6112. protected $component;
  6113. /** @var string callback arg - the area of submission files we export */
  6114. protected $area;
  6115. /** @var int callback arg - the id of file we export */
  6116. protected $fileid;
  6117. /** @var int callback arg - the cmid of the assignment we export */
  6118. protected $cmid;
  6119. /** @var string callback arg - the plugintype of the editor we export */
  6120. protected $plugin;
  6121. /** @var string callback arg - the name of the editor field we export */
  6122. protected $editor;
  6123. /**
  6124. * Callback arg for a single file export.
  6125. */
  6126. public static function expected_callbackargs() {
  6127. return array(
  6128. 'cmid' => true,
  6129. 'sid' => false,
  6130. 'area' => false,
  6131. 'component' => false,
  6132. 'fileid' => false,
  6133. 'plugin' => false,
  6134. 'editor' => false,
  6135. );
  6136. }
  6137. /**
  6138. * The constructor.
  6139. *
  6140. * @param array $callbackargs
  6141. */
  6142. public function __construct($callbackargs) {
  6143. parent::__construct($callbackargs);
  6144. $this->cm = get_coursemodule_from_id('assign', $this->cmid, 0, false, MUST_EXIST);
  6145. }
  6146. /**
  6147. * Load data needed for the portfolio export.
  6148. *
  6149. * If the assignment type implements portfolio_load_data(), the processing is delegated
  6150. * to it. Otherwise, the caller must provide either fileid (to export single file) or
  6151. * submissionid and filearea (to export all data attached to the given submission file area)
  6152. * via callback arguments.
  6153. *
  6154. * @throws portfolio_caller_exception
  6155. */
  6156. public function load_data() {
  6157. $context = context_module::instance($this->cmid);
  6158. if (empty($this->fileid)) {
  6159. if (empty($this->sid) || empty($this->area)) {
  6160. throw new portfolio_caller_exception('invalidfileandsubmissionid', 'mod_assign');
  6161. }
  6162. }
  6163. // Export either an area of files or a single file (see function for more detail).
  6164. // The first arg is an id or null. If it is an id, the rest of the args are ignored.
  6165. // If it is null, the rest of the args are used to load a list of files from get_areafiles.
  6166. $this->set_file_and_format_data($this->fileid,
  6167. $context->id,
  6168. $this->component,
  6169. $this->area,
  6170. $this->sid,
  6171. 'timemodified',
  6172. false);
  6173. }
  6174. /**
  6175. * Prepares the package up before control is passed to the portfolio plugin.
  6176. *
  6177. * @throws portfolio_caller_exception
  6178. * @return mixed
  6179. */
  6180. public function prepare_package() {
  6181. if ($this->plugin && $this->editor) {
  6182. $options = portfolio_format_text_options();
  6183. $context = context_module::instance($this->cmid);
  6184. $options->context = $context;
  6185. $plugin = $this->get_submission_plugin();
  6186. $text = $plugin->get_editor_text($this->editor, $this->sid);
  6187. $format = $plugin->get_editor_format($this->editor, $this->sid);
  6188. $html = format_text($text, $format, $options);
  6189. $html = portfolio_rewrite_pluginfile_urls($html,
  6190. $context->id,
  6191. 'mod_assign',
  6192. $this->area,
  6193. $this->sid,
  6194. $this->exporter->get('format'));
  6195. $exporterclass = $this->exporter->get('formatclass');
  6196. if (in_array($exporterclass, array(PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_RICHHTML))) {
  6197. if ($files = $this->exporter->get('caller')->get('multifiles')) {
  6198. foreach ($files as $file) {
  6199. $this->exporter->copy_existing_file($file);
  6200. }
  6201. }
  6202. return $this->exporter->write_new_file($html, 'assignment.html', !empty($files));
  6203. } else if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
  6204. $leapwriter = $this->exporter->get('format')->leap2a_writer();
  6205. $entry = new portfolio_format_leap2a_entry($this->area . $this->cmid,
  6206. $context->get_context_name(),
  6207. 'resource',
  6208. $html);
  6209. $entry->add_category('web', 'resource_type');
  6210. $entry->author = $this->user;
  6211. $leapwriter->add_entry($entry);
  6212. if ($files = $this->exporter->get('caller')->get('multifiles')) {
  6213. $leapwriter->link_files($entry, $files, $this->area . $this->cmid . 'file');
  6214. foreach ($files as $file) {
  6215. $this->exporter->copy_existing_file($file);
  6216. }
  6217. }
  6218. return $this->exporter->write_new_file($leapwriter->to_xml(),
  6219. $this->exporter->get('format')->manifest_name(),
  6220. true);
  6221. } else {
  6222. debugging('invalid format class: ' . $this->exporter->get('formatclass'));
  6223. }
  6224. }
  6225. if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
  6226. $leapwriter = $this->exporter->get('format')->leap2a_writer();
  6227. $files = array();
  6228. if ($this->singlefile) {
  6229. $files[] = $this->singlefile;
  6230. } else if ($this->multifiles) {
  6231. $files = $this->multifiles;
  6232. } else {
  6233. throw new portfolio_caller_exception('invalidpreparepackagefile',
  6234. 'portfolio',
  6235. $this->get_return_url());
  6236. }
  6237. $entryids = array();
  6238. foreach ($files as $file) {
  6239. $entry = new portfolio_format_leap2a_file($file->get_filename(), $file);
  6240. $entry->author = $this->user;
  6241. $leapwriter->add_entry($entry);
  6242. $this->exporter->copy_existing_file($file);
  6243. $entryids[] = $entry->id;
  6244. }
  6245. if (count($files) > 1) {
  6246. $baseid = 'assign' . $this->cmid . $this->area;
  6247. $context = context_module::instance($this->cmid);
  6248. // If we have multiple files, they should be grouped together into a folder.
  6249. $entry = new portfolio_format_leap2a_entry($baseid . 'group',
  6250. $context->get_context_name(),
  6251. 'selection');
  6252. $leapwriter->add_entry($entry);
  6253. $leapwriter->make_selection($entry, $entryids, 'Folder');
  6254. }
  6255. return $this->exporter->write_new_file($leapwriter->to_xml(),
  6256. $this->exporter->get('format')->manifest_name(),
  6257. true);
  6258. }
  6259. return $this->prepare_package_file();
  6260. }
  6261. /**
  6262. * Fetch the plugin by its type.
  6263. *
  6264. * @return assign_submission_plugin
  6265. */
  6266. protected function get_submission_plugin() {
  6267. global $CFG;
  6268. if (!$this->plugin || !$this->cmid) {
  6269. return null;
  6270. }
  6271. require_once($CFG->dirroot . '/mod/assign/locallib.php');
  6272. $context = context_module::instance($this->cmid);
  6273. $assignment = new assign($context, null, null);
  6274. return $assignment->get_submission_plugin_by_type($this->plugin);
  6275. }
  6276. /**
  6277. * Calculate a sha1 has of either a single file or a list
  6278. * of files based on the data set by load_data.
  6279. *
  6280. * @return string
  6281. */
  6282. public function get_sha1() {
  6283. if ($this->plugin && $this->editor) {
  6284. $plugin = $this->get_submission_plugin();
  6285. $options = portfolio_format_text_options();
  6286. $options->context = context_module::instance($this->cmid);
  6287. $text = format_text($plugin->get_editor_text($this->editor, $this->sid),
  6288. $plugin->get_editor_format($this->editor, $this->sid),
  6289. $options);
  6290. $textsha1 = sha1($text);
  6291. $filesha1 = '';
  6292. try {
  6293. $filesha1 = $this->get_sha1_file();
  6294. } catch (portfolio_caller_exception $e) {
  6295. // No files.
  6296. }
  6297. return sha1($textsha1 . $filesha1);
  6298. }
  6299. return $this->get_sha1_file();
  6300. }
  6301. /**
  6302. * Calculate the time to transfer either a single file or a list
  6303. * of files based on the data set by load_data.
  6304. *
  6305. * @return int
  6306. */
  6307. public function expected_time() {
  6308. return $this->expected_time_file();
  6309. }
  6310. /**
  6311. * Checking the permissions.
  6312. *
  6313. * @return bool
  6314. */
  6315. public function check_permissions() {
  6316. $context = context_module::instance($this->cmid);
  6317. return has_capability('mod/assign:exportownsubmission', $context);
  6318. }
  6319. /**
  6320. * Display a module name.
  6321. *
  6322. * @return string
  6323. */
  6324. public static function display_name() {
  6325. return get_string('modulename', 'assign');
  6326. }
  6327. /**
  6328. * Return array of formats supported by this portfolio call back.
  6329. *
  6330. * @return array
  6331. */
  6332. public static function base_supported_formats() {
  6333. return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
  6334. }
  6335. }