Commit | Line | Data |
---|---|---|
bbd0e548 DW |
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 | /** | |
18 | * This file contains the definition for the class assignment | |
19 | * | |
20 | * This class provides all the functionality for the new assign module. | |
21 | * | |
22 | * @package mod_assign | |
23 | * @copyright 2012 NetSpot {@link http://www.netspot.com.au} | |
24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
25 | */ | |
26 | ||
27 | defined('MOODLE_INTERNAL') || die(); | |
28 | ||
e5403f8c | 29 | // Assignment submission statuses. |
9e3eee67 | 30 | define('ASSIGN_SUBMISSION_STATUS_NEW', 'new'); |
df211804 | 31 | define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened'); |
e5403f8c DW |
32 | define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft'); |
33 | define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted'); | |
bbd0e548 | 34 | |
e5403f8c | 35 | // Search filters for grading page. |
064f1503 | 36 | define('ASSIGN_FILTER_NONE', 'none'); |
bbd0e548 | 37 | define('ASSIGN_FILTER_SUBMITTED', 'submitted'); |
c80d59f2 | 38 | define('ASSIGN_FILTER_NOT_SUBMITTED', 'notsubmitted'); |
bbd0e548 | 39 | define('ASSIGN_FILTER_SINGLE_USER', 'singleuser'); |
064f1503 DW |
40 | define('ASSIGN_FILTER_REQUIRE_GRADING', 'requiregrading'); |
41 | define('ASSIGN_FILTER_GRANTED_EXTENSION', 'grantedextension'); | |
bbd0e548 | 42 | |
c80d59f2 JF |
43 | // Marker filter for grading page. |
44 | define('ASSIGN_MARKER_FILTER_NO_MARKER', -1); | |
45 | ||
df211804 DW |
46 | // Reopen attempt methods. |
47 | define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none'); | |
48 | define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual'); | |
49 | define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass'); | |
50 | ||
51 | // Special value means allow unlimited attempts. | |
52 | define('ASSIGN_UNLIMITED_ATTEMPTS', -1); | |
53 | ||
2d1075e3 JO |
54 | // Special value means no grade has been set. |
55 | define('ASSIGN_GRADE_NOT_SET', -1); | |
56 | ||
bd3ee807 MN |
57 | // Grading states. |
58 | define('ASSIGN_GRADING_STATUS_GRADED', 'graded'); | |
59 | define('ASSIGN_GRADING_STATUS_NOT_GRADED', 'notgraded'); | |
60 | ||
f8d107b3 DM |
61 | // Marking workflow states. |
62 | define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked'); | |
63 | define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking'); | |
64 | define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview'); | |
65 | define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview'); | |
66 | define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease'); | |
67 | define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released'); | |
68 | ||
d685b959 IT |
69 | /** ASSIGN_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */ |
70 | define("ASSIGN_MAX_EVENT_LENGTH", "432000"); | |
71 | ||
7faf78cb HB |
72 | // Name of file area for intro attachments. |
73 | define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment'); | |
74 | ||
294dce67 MN |
75 | // Event types. |
76 | define('ASSIGN_EVENT_TYPE_DUE', 'due'); | |
77 | define('ASSIGN_EVENT_TYPE_GRADINGDUE', 'gradingdue'); | |
78 | define('ASSIGN_EVENT_TYPE_OPEN', 'open'); | |
79 | define('ASSIGN_EVENT_TYPE_CLOSE', 'close'); | |
80 | ||
e5403f8c DW |
81 | require_once($CFG->libdir . '/accesslib.php'); |
82 | require_once($CFG->libdir . '/formslib.php'); | |
bbd0e548 | 83 | require_once($CFG->dirroot . '/repository/lib.php'); |
e5403f8c DW |
84 | require_once($CFG->dirroot . '/mod/assign/mod_form.php'); |
85 | require_once($CFG->libdir . '/gradelib.php'); | |
86 | require_once($CFG->dirroot . '/grade/grading/lib.php'); | |
87 | require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php'); | |
88 | require_once($CFG->dirroot . '/mod/assign/submissionplugin.php'); | |
89 | require_once($CFG->dirroot . '/mod/assign/renderable.php'); | |
90 | require_once($CFG->dirroot . '/mod/assign/gradingtable.php'); | |
37743241 | 91 | require_once($CFG->libdir . '/portfolio/caller.php'); |
bbd0e548 | 92 | |
bb690849 DW |
93 | use \mod_assign\output\grading_app; |
94 | ||
bbd0e548 DW |
95 | /** |
96 | * Standard base class for mod_assign (assignment types). | |
97 | * | |
98 | * @package mod_assign | |
99 | * @copyright 2012 NetSpot {@link http://www.netspot.com.au} | |
100 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
101 | */ | |
102 | class assign { | |
103 | ||
bbd0e548 DW |
104 | /** @var stdClass the assignment record that contains the global settings for this assign instance */ |
105 | private $instance; | |
106 | ||
db381d5c JD |
107 | /** @var array $var array an array containing per-user assignment records, each having calculated properties (e.g. dates) */ |
108 | private $userinstances = []; | |
109 | ||
db8514bc | 110 | /** @var grade_item the grade_item record for this assign instance's primary grade item. */ |
0d64c75f PN |
111 | private $gradeitem; |
112 | ||
e5403f8c DW |
113 | /** @var context the context of the course module for this assign instance |
114 | * (or just the course if we are creating a new one) | |
115 | */ | |
bbd0e548 DW |
116 | private $context; |
117 | ||
118 | /** @var stdClass the course this assign instance belongs to */ | |
119 | private $course; | |
bc5a657b | 120 | |
cfc81f03 DW |
121 | /** @var stdClass the admin config for all assign instances */ |
122 | private $adminconfig; | |
123 | ||
bbd0e548 DW |
124 | /** @var assign_renderer the custom renderer for this module */ |
125 | private $output; | |
126 | ||
c13ac85d | 127 | /** @var cm_info the course module for this assign instance */ |
bbd0e548 DW |
128 | private $coursemodule; |
129 | ||
e5403f8c DW |
130 | /** @var array cache for things like the coursemodule name or the scale menu - |
131 | * only lives for a single request. | |
132 | */ | |
bbd0e548 DW |
133 | private $cache; |
134 | ||
135 | /** @var array list of the installed submission plugins */ | |
136 | private $submissionplugins; | |
137 | ||
138 | /** @var array list of the installed feedback plugins */ | |
139 | private $feedbackplugins; | |
140 | ||
e5403f8c DW |
141 | /** @var string action to be used to return to this page |
142 | * (without repeating any form submissions etc). | |
143 | */ | |
bbd0e548 DW |
144 | private $returnaction = 'view'; |
145 | ||
146 | /** @var array params to be used to return to this page */ | |
147 | private $returnparams = array(); | |
148 | ||
149 | /** @var string modulename prevents excessive calls to get_string */ | |
f5b32abe | 150 | private static $modulename = null; |
bbd0e548 DW |
151 | |
152 | /** @var string modulenameplural prevents excessive calls to get_string */ | |
f5b32abe | 153 | private static $modulenameplural = null; |
bbd0e548 | 154 | |
f8d107b3 DM |
155 | /** @var array of marking workflow states for the current user */ |
156 | private $markingworkflowstates = null; | |
157 | ||
4c4c7b3f | 158 | /** @var bool whether to exclude users with inactive enrolment */ |
1ecb8044 | 159 | private $showonlyactiveenrol = null; |
4c4c7b3f | 160 | |
1a51661b | 161 | /** @var string A key used to identify userlists created by this object. */ |
3fca6937 NM |
162 | private $useridlistid = null; |
163 | ||
c46db93c DW |
164 | /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */ |
165 | private $participants = array(); | |
166 | ||
bc006a66 DM |
167 | /** @var array cached list of user groups when team submissions are enabled. The cache key will be the user. */ |
168 | private $usersubmissiongroups = array(); | |
169 | ||
ee5d4eef AH |
170 | /** @var array cached list of user groups. The cache key will be the user. */ |
171 | private $usergroups = array(); | |
172 | ||
6d6e297b DM |
173 | /** @var array cached list of IDs of users who share group membership with the user. The cache key will be the user. */ |
174 | private $sharedgroupmembers = array(); | |
175 | ||
75c21e7e AG |
176 | /** |
177 | * @var stdClass The most recent team submission. Used to determine additional attempt numbers and whether | |
178 | * to update the gradebook. | |
179 | */ | |
180 | private $mostrecentteamsubmission = null; | |
181 | ||
8dc45b37 MG |
182 | /** @var array Array of error messages encountered during the execution of assignment related operations. */ |
183 | private $errors = array(); | |
184 | ||
bbd0e548 | 185 | /** |
e5403f8c | 186 | * Constructor for the base assign class. |
bbd0e548 | 187 | * |
c13ac85d | 188 | * Note: For $coursemodule you can supply a stdclass if you like, but it |
189 | * will be more efficient to supply a cm_info object. | |
190 | * | |
e5403f8c DW |
191 | * @param mixed $coursemodulecontext context|null the course module context |
192 | * (or the course context if the coursemodule has not been | |
193 | * created yet). | |
194 | * @param mixed $coursemodule the current course module if it was already loaded, | |
195 | * otherwise this class will load one from the context as required. | |
196 | * @param mixed $course the current course if it was already loaded, | |
197 | * otherwise this class will load one from the context as required. | |
bbd0e548 DW |
198 | */ |
199 | public function __construct($coursemodulecontext, $coursemodule, $course) { | |
1a51661b DM |
200 | global $SESSION; |
201 | ||
bbd0e548 | 202 | $this->context = $coursemodulecontext; |
bbd0e548 | 203 | $this->course = $course; |
e5403f8c | 204 | |
c13ac85d | 205 | // Ensure that $this->coursemodule is a cm_info object (or null). |
206 | $this->coursemodule = cm_info::create($coursemodule); | |
207 | ||
e5403f8c DW |
208 | // Temporary cache only lives for a single request - used to reduce db lookups. |
209 | $this->cache = array(); | |
bbd0e548 DW |
210 | |
211 | $this->submissionplugins = $this->load_plugins('assignsubmission'); | |
212 | $this->feedbackplugins = $this->load_plugins('assignfeedback'); | |
3fca6937 NM |
213 | |
214 | // Extra entropy is required for uniqid() to work on cygwin. | |
215 | $this->useridlistid = clean_param(uniqid('', true), PARAM_ALPHANUM); | |
1a51661b DM |
216 | |
217 | if (!isset($SESSION->mod_assign_useridlist)) { | |
218 | $SESSION->mod_assign_useridlist = []; | |
219 | } | |
bbd0e548 DW |
220 | } |
221 | ||
222 | /** | |
e5403f8c | 223 | * Set the action and parameters that can be used to return to the current page. |
bbd0e548 DW |
224 | * |
225 | * @param string $action The action for the current page | |
e5403f8c DW |
226 | * @param array $params An array of name value pairs which form the parameters |
227 | * to return to the current page. | |
bbd0e548 DW |
228 | * @return void |
229 | */ | |
230 | public function register_return_link($action, $params) { | |
d04557b3 DW |
231 | global $PAGE; |
232 | $params['action'] = $action; | |
77a53060 DW |
233 | $cm = $this->get_course_module(); |
234 | if ($cm) { | |
235 | $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $cm->id)); | |
236 | } else { | |
237 | $currenturl = new moodle_url('/mod/assign/index.php', array('id' => $this->get_course()->id)); | |
238 | } | |
d04557b3 DW |
239 | |
240 | $currenturl->params($params); | |
241 | $PAGE->set_url($currenturl); | |
bbd0e548 DW |
242 | } |
243 | ||
244 | /** | |
e5403f8c DW |
245 | * Return an action that can be used to get back to the current page. |
246 | * | |
bbd0e548 DW |
247 | * @return string action |
248 | */ | |
249 | public function get_return_action() { | |
d04557b3 DW |
250 | global $PAGE; |
251 | ||
3216f616 JL |
252 | // Web services don't set a URL, we should avoid debugging when ussing the url object. |
253 | if (!WS_SERVER) { | |
254 | $params = $PAGE->url->params(); | |
255 | } | |
d04557b3 | 256 | |
c2114099 DW |
257 | if (!empty($params['action'])) { |
258 | return $params['action']; | |
259 | } | |
260 | return ''; | |
bbd0e548 DW |
261 | } |
262 | ||
263 | /** | |
e5403f8c DW |
264 | * Based on the current assignment settings should we display the intro. |
265 | * | |
bbd0e548 DW |
266 | * @return bool showintro |
267 | */ | |
7faf78cb | 268 | public function show_intro() { |
bbd0e548 DW |
269 | if ($this->get_instance()->alwaysshowdescription || |
270 | time() > $this->get_instance()->allowsubmissionsfromdate) { | |
271 | return true; | |
272 | } | |
273 | return false; | |
274 | } | |
275 | ||
276 | /** | |
e5403f8c DW |
277 | * Return a list of parameters that can be used to get back to the current page. |
278 | * | |
bbd0e548 DW |
279 | * @return array params |
280 | */ | |
281 | public function get_return_params() { | |
d04557b3 DW |
282 | global $PAGE; |
283 | ||
96a037f7 ALH |
284 | $params = array(); |
285 | if (!WS_SERVER) { | |
286 | $params = $PAGE->url->params(); | |
287 | } | |
c2114099 DW |
288 | unset($params['id']); |
289 | unset($params['action']); | |
290 | return $params; | |
bbd0e548 DW |
291 | } |
292 | ||
293 | /** | |
e5403f8c DW |
294 | * Set the submitted form data. |
295 | * | |
bbd0e548 DW |
296 | * @param stdClass $data The form data (instance) |
297 | */ | |
298 | public function set_instance(stdClass $data) { | |
299 | $this->instance = $data; | |
300 | } | |
301 | ||
302 | /** | |
e5403f8c DW |
303 | * Set the context. |
304 | * | |
bbd0e548 DW |
305 | * @param context $context The new context |
306 | */ | |
307 | public function set_context(context $context) { | |
308 | $this->context = $context; | |
309 | } | |
310 | ||
311 | /** | |
e5403f8c DW |
312 | * Set the course data. |
313 | * | |
bbd0e548 DW |
314 | * @param stdClass $course The course data |
315 | */ | |
316 | public function set_course(stdClass $course) { | |
317 | $this->course = $course; | |
318 | } | |
319 | ||
8dc45b37 MG |
320 | /** |
321 | * Set error message. | |
322 | * | |
323 | * @param string $message The error message | |
324 | */ | |
325 | protected function set_error_message(string $message) { | |
326 | $this->errors[] = $message; | |
327 | } | |
328 | ||
329 | /** | |
330 | * Get error messages. | |
331 | * | |
332 | * @return array The array of error messages | |
333 | */ | |
334 | protected function get_error_messages(): array { | |
335 | return $this->errors; | |
336 | } | |
337 | ||
bbd0e548 | 338 | /** |
e5403f8c DW |
339 | * Get list of feedback plugins installed. |
340 | * | |
bbd0e548 DW |
341 | * @return array |
342 | */ | |
343 | public function get_feedback_plugins() { | |
344 | return $this->feedbackplugins; | |
345 | } | |
346 | ||
347 | /** | |
e5403f8c DW |
348 | * Get list of submission plugins installed. |
349 | * | |
bbd0e548 DW |
350 | * @return array |
351 | */ | |
352 | public function get_submission_plugins() { | |
353 | return $this->submissionplugins; | |
354 | } | |
355 | ||
b473171a DW |
356 | /** |
357 | * Is blind marking enabled and reveal identities not set yet? | |
358 | * | |
359 | * @return bool | |
360 | */ | |
361 | public function is_blind_marking() { | |
362 | return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities; | |
363 | } | |
364 | ||
f14afd29 AW |
365 | /** |
366 | * Is hidden grading enabled? | |
367 | * | |
368 | * This just checks the assignment settings. Remember to check | |
369 | * the user has the 'showhiddengrader' capability too | |
370 | * | |
371 | * @return bool | |
372 | */ | |
373 | public function is_hidden_grader() { | |
374 | return $this->get_instance()->hidegrader; | |
375 | } | |
376 | ||
b473171a DW |
377 | /** |
378 | * Does an assignment have submission(s) or grade(s) already? | |
379 | * | |
380 | * @return bool | |
381 | */ | |
382 | public function has_submissions_or_grades() { | |
383 | $allgrades = $this->count_grades(); | |
384 | $allsubmissions = $this->count_submissions(); | |
385 | if (($allgrades == 0) && ($allsubmissions == 0)) { | |
386 | return false; | |
387 | } | |
388 | return true; | |
389 | } | |
bbd0e548 DW |
390 | |
391 | /** | |
e5403f8c DW |
392 | * Get a specific submission plugin by its type. |
393 | * | |
bbd0e548 DW |
394 | * @param string $subtype assignsubmission | assignfeedback |
395 | * @param string $type | |
396 | * @return mixed assign_plugin|null | |
397 | */ | |
df47b77f | 398 | public function get_plugin_by_type($subtype, $type) { |
bbd0e548 DW |
399 | $shortsubtype = substr($subtype, strlen('assign')); |
400 | $name = $shortsubtype . 'plugins'; | |
d0d4796b DW |
401 | if ($name != 'feedbackplugins' && $name != 'submissionplugins') { |
402 | return null; | |
403 | } | |
bbd0e548 DW |
404 | $pluginlist = $this->$name; |
405 | foreach ($pluginlist as $plugin) { | |
406 | if ($plugin->get_type() == $type) { | |
407 | return $plugin; | |
408 | } | |
409 | } | |
410 | return null; | |
411 | } | |
412 | ||
413 | /** | |
e5403f8c DW |
414 | * Get a feedback plugin by type. |
415 | * | |
bbd0e548 DW |
416 | * @param string $type - The type of plugin e.g comments |
417 | * @return mixed assign_feedback_plugin|null | |
418 | */ | |
419 | public function get_feedback_plugin_by_type($type) { | |
420 | return $this->get_plugin_by_type('assignfeedback', $type); | |
421 | } | |
422 | ||
423 | /** | |
e5403f8c DW |
424 | * Get a submission plugin by type. |
425 | * | |
bbd0e548 DW |
426 | * @param string $type - The type of plugin e.g comments |
427 | * @return mixed assign_submission_plugin|null | |
428 | */ | |
429 | public function get_submission_plugin_by_type($type) { | |
430 | return $this->get_plugin_by_type('assignsubmission', $type); | |
431 | } | |
432 | ||
433 | /** | |
e5403f8c DW |
434 | * Load the plugins from the sub folders under subtype. |
435 | * | |
bbd0e548 DW |
436 | * @param string $subtype - either submission or feedback |
437 | * @return array - The sorted list of plugins | |
438 | */ | |
e5e89cac | 439 | public function load_plugins($subtype) { |
bbd0e548 DW |
440 | global $CFG; |
441 | $result = array(); | |
442 | ||
bd3b3bba | 443 | $names = core_component::get_plugin_list($subtype); |
bbd0e548 DW |
444 | |
445 | foreach ($names as $name => $path) { | |
446 | if (file_exists($path . '/locallib.php')) { | |
447 | require_once($path . '/locallib.php'); | |
448 | ||
449 | $shortsubtype = substr($subtype, strlen('assign')); | |
450 | $pluginclass = 'assign_' . $shortsubtype . '_' . $name; | |
451 | ||
452 | $plugin = new $pluginclass($this, $name); | |
453 | ||
454 | if ($plugin instanceof assign_plugin) { | |
455 | $idx = $plugin->get_sort_order(); | |
e5403f8c DW |
456 | while (array_key_exists($idx, $result)) { |
457 | $idx +=1; | |
458 | } | |
bbd0e548 DW |
459 | $result[$idx] = $plugin; |
460 | } | |
461 | } | |
462 | } | |
463 | ksort($result); | |
464 | return $result; | |
465 | } | |
466 | ||
bbd0e548 DW |
467 | /** |
468 | * Display the assignment, used by view.php | |
469 | * | |
470 | * The assignment is displayed differently depending on your role, | |
471 | * the settings for the assignment and the status of the assignment. | |
e5403f8c | 472 | * |
bbd0e548 | 473 | * @param string $action The current action if any. |
bb690849 | 474 | * @param array $args Optional arguments to pass to the view (instead of getting them from GET and POST). |
df211804 | 475 | * @return string - The page output. |
bbd0e548 | 476 | */ |
bb690849 | 477 | public function view($action='', $args = array()) { |
e61036f4 | 478 | global $PAGE; |
bbd0e548 DW |
479 | |
480 | $o = ''; | |
481 | $mform = null; | |
34b8f3a8 | 482 | $notices = array(); |
34e338a4 | 483 | $nextpageparams = array(); |
bbd0e548 | 484 | |
34e338a4 MN |
485 | if (!empty($this->get_course_module()->id)) { |
486 | $nextpageparams['id'] = $this->get_course_module()->id; | |
487 | } | |
d04557b3 | 488 | |
34b8f3a8 | 489 | // Handle form submissions first. |
bbd0e548 DW |
490 | if ($action == 'savesubmission') { |
491 | $action = 'editsubmission'; | |
34b8f3a8 | 492 | if ($this->process_save_submission($mform, $notices)) { |
d04557b3 | 493 | $action = 'redirect'; |
37fc47f1 DW |
494 | if ($this->can_grade()) { |
495 | $nextpageparams['action'] = 'grading'; | |
496 | } else { | |
497 | $nextpageparams['action'] = 'view'; | |
498 | } | |
bbd0e548 | 499 | } |
df211804 DW |
500 | } else if ($action == 'editprevioussubmission') { |
501 | $action = 'editsubmission'; | |
502 | if ($this->process_copy_previous_attempt($notices)) { | |
503 | $action = 'redirect'; | |
504 | $nextpageparams['action'] = 'editsubmission'; | |
505 | } | |
9e795179 | 506 | } else if ($action == 'lock') { |
05a6445a | 507 | $this->process_lock_submission(); |
d04557b3 DW |
508 | $action = 'redirect'; |
509 | $nextpageparams['action'] = 'grading'; | |
37fc47f1 DW |
510 | } else if ($action == 'removesubmission') { |
511 | $this->process_remove_submission(); | |
512 | $action = 'redirect'; | |
513 | if ($this->can_grade()) { | |
514 | $nextpageparams['action'] = 'grading'; | |
515 | } else { | |
516 | $nextpageparams['action'] = 'view'; | |
517 | } | |
df211804 DW |
518 | } else if ($action == 'addattempt') { |
519 | $this->process_add_attempt(required_param('userid', PARAM_INT)); | |
520 | $action = 'redirect'; | |
521 | $nextpageparams['action'] = 'grading'; | |
9e795179 | 522 | } else if ($action == 'reverttodraft') { |
bbd0e548 | 523 | $this->process_revert_to_draft(); |
d04557b3 DW |
524 | $action = 'redirect'; |
525 | $nextpageparams['action'] = 'grading'; | |
9e795179 | 526 | } else if ($action == 'unlock') { |
05a6445a | 527 | $this->process_unlock_submission(); |
d04557b3 DW |
528 | $action = 'redirect'; |
529 | $nextpageparams['action'] = 'grading'; | |
f8d107b3 DM |
530 | } else if ($action == 'setbatchmarkingworkflowstate') { |
531 | $this->process_set_batch_marking_workflow_state(); | |
532 | $action = 'redirect'; | |
533 | $nextpageparams['action'] = 'grading'; | |
534 | } else if ($action == 'setbatchmarkingallocation') { | |
535 | $this->process_set_batch_marking_allocation(); | |
536 | $action = 'redirect'; | |
537 | $nextpageparams['action'] = 'grading'; | |
9e795179 | 538 | } else if ($action == 'confirmsubmit') { |
94f26900 | 539 | $action = 'submit'; |
57fbd5f9 | 540 | if ($this->process_submit_for_grading($mform, $notices)) { |
d04557b3 DW |
541 | $action = 'redirect'; |
542 | $nextpageparams['action'] = 'view'; | |
57fbd5f9 DW |
543 | } else if ($notices) { |
544 | $action = 'viewsubmitforgradingerror'; | |
545 | } | |
546 | } else if ($action == 'submitotherforgrading') { | |
547 | if ($this->process_submit_other_for_grading($mform, $notices)) { | |
548 | $action = 'redirect'; | |
549 | $nextpageparams['action'] = 'grading'; | |
550 | } else { | |
551 | $action = 'viewsubmitforgradingerror'; | |
94f26900 | 552 | } |
df47b77f DW |
553 | } else if ($action == 'gradingbatchoperation') { |
554 | $action = $this->process_grading_batch_operation($mform); | |
d04557b3 DW |
555 | if ($action == 'grading') { |
556 | $action = 'redirect'; | |
557 | $nextpageparams['action'] = 'grading'; | |
558 | } | |
9e795179 | 559 | } else if ($action == 'submitgrade') { |
ba30fe35 | 560 | if (optional_param('saveandshownext', null, PARAM_RAW)) { |
e5403f8c | 561 | // Save and show next. |
bbd0e548 DW |
562 | $action = 'grade'; |
563 | if ($this->process_save_grade($mform)) { | |
d04557b3 DW |
564 | $action = 'redirect'; |
565 | $nextpageparams['action'] = 'grade'; | |
566 | $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1; | |
3fca6937 | 567 | $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM); |
bbd0e548 | 568 | } |
ba30fe35 | 569 | } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) { |
d04557b3 DW |
570 | $action = 'redirect'; |
571 | $nextpageparams['action'] = 'grade'; | |
572 | $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1; | |
3fca6937 | 573 | $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM); |
ba30fe35 | 574 | } else if (optional_param('nosaveandnext', null, PARAM_RAW)) { |
d04557b3 DW |
575 | $action = 'redirect'; |
576 | $nextpageparams['action'] = 'grade'; | |
577 | $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1; | |
3fca6937 | 578 | $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM); |
ba30fe35 | 579 | } else if (optional_param('savegrade', null, PARAM_RAW)) { |
e5403f8c | 580 | // Save changes button. |
bbd0e548 DW |
581 | $action = 'grade'; |
582 | if ($this->process_save_grade($mform)) { | |
082ad257 JF |
583 | $action = 'redirect'; |
584 | $nextpageparams['action'] = 'savegradingresult'; | |
bbd0e548 DW |
585 | } |
586 | } else { | |
e5403f8c | 587 | // Cancel button. |
d04557b3 DW |
588 | $action = 'redirect'; |
589 | $nextpageparams['action'] = 'grading'; | |
bbd0e548 | 590 | } |
9e795179 | 591 | } else if ($action == 'quickgrade') { |
bf78ebd6 DW |
592 | $message = $this->process_save_quick_grades(); |
593 | $action = 'quickgradingresult'; | |
9e795179 | 594 | } else if ($action == 'saveoptions') { |
bbd0e548 | 595 | $this->process_save_grading_options(); |
d04557b3 DW |
596 | $action = 'redirect'; |
597 | $nextpageparams['action'] = 'grading'; | |
9e795179 DW |
598 | } else if ($action == 'saveextension') { |
599 | $action = 'grantextension'; | |
600 | if ($this->process_save_extension($mform)) { | |
d04557b3 DW |
601 | $action = 'redirect'; |
602 | $nextpageparams['action'] = 'grading'; | |
9e795179 | 603 | } |
b473171a DW |
604 | } else if ($action == 'revealidentitiesconfirm') { |
605 | $this->process_reveal_identities(); | |
d04557b3 DW |
606 | $action = 'redirect'; |
607 | $nextpageparams['action'] = 'grading'; | |
bbd0e548 DW |
608 | } |
609 | ||
d04557b3 | 610 | $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT), |
3fca6937 | 611 | 'useridlistid' => optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM)); |
bbd0e548 DW |
612 | $this->register_return_link($action, $returnparams); |
613 | ||
e61036f4 DB |
614 | // Include any page action as part of the body tag CSS id. |
615 | if (!empty($action)) { | |
616 | $PAGE->set_pagetype('mod-assign-' . $action); | |
617 | } | |
34b8f3a8 | 618 | // Now show the right view page. |
d04557b3 DW |
619 | if ($action == 'redirect') { |
620 | $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams); | |
8dc45b37 MG |
621 | $messages = ''; |
622 | $messagetype = \core\output\notification::NOTIFY_INFO; | |
623 | $errors = $this->get_error_messages(); | |
624 | if (!empty($errors)) { | |
625 | $messages = html_writer::alist($errors, ['class' => 'mb-1 mt-1']); | |
626 | $messagetype = \core\output\notification::NOTIFY_ERROR; | |
627 | } | |
628 | redirect($nextpageurl, $messages, null, $messagetype); | |
d04557b3 | 629 | return; |
df211804 | 630 | } else if ($action == 'savegradingresult') { |
082ad257 | 631 | $message = get_string('gradingchangessaved', 'assign'); |
df211804 | 632 | $o .= $this->view_savegrading_result($message); |
bf78ebd6 DW |
633 | } else if ($action == 'quickgradingresult') { |
634 | $mform = null; | |
635 | $o .= $this->view_quickgrading_result($message); | |
bb690849 DW |
636 | } else if ($action == 'gradingpanel') { |
637 | $o .= $this->view_single_grading_panel($args); | |
bbd0e548 DW |
638 | } else if ($action == 'grade') { |
639 | $o .= $this->view_single_grade_page($mform); | |
640 | } else if ($action == 'viewpluginassignfeedback') { | |
641 | $o .= $this->view_plugin_content('assignfeedback'); | |
642 | } else if ($action == 'viewpluginassignsubmission') { | |
643 | $o .= $this->view_plugin_content('assignsubmission'); | |
644 | } else if ($action == 'editsubmission') { | |
34b8f3a8 | 645 | $o .= $this->view_edit_submission_page($mform, $notices); |
bb690849 DW |
646 | } else if ($action == 'grader') { |
647 | $o .= $this->view_grader(); | |
bbd0e548 DW |
648 | } else if ($action == 'grading') { |
649 | $o .= $this->view_grading_page(); | |
650 | } else if ($action == 'downloadall') { | |
651 | $o .= $this->download_submissions(); | |
652 | } else if ($action == 'submit') { | |
94f26900 | 653 | $o .= $this->check_submit_for_grading($mform); |
9e795179 DW |
654 | } else if ($action == 'grantextension') { |
655 | $o .= $this->view_grant_extension($mform); | |
b473171a DW |
656 | } else if ($action == 'revealidentities') { |
657 | $o .= $this->view_reveal_identities_confirm($mform); | |
37fc47f1 DW |
658 | } else if ($action == 'removesubmissionconfirm') { |
659 | $o .= $this->view_remove_submission_confirm(); | |
df47b77f DW |
660 | } else if ($action == 'plugingradingbatchoperation') { |
661 | $o .= $this->view_plugin_grading_batch_operation($mform); | |
7a2b911c | 662 | } else if ($action == 'viewpluginpage') { |
df47b77f | 663 | $o .= $this->view_plugin_page(); |
64220210 DW |
664 | } else if ($action == 'viewcourseindex') { |
665 | $o .= $this->view_course_index(); | |
f8d107b3 DM |
666 | } else if ($action == 'viewbatchsetmarkingworkflowstate') { |
667 | $o .= $this->view_batch_set_workflow_state($mform); | |
668 | } else if ($action == 'viewbatchmarkingallocation') { | |
669 | $o .= $this->view_batch_markingallocation($mform); | |
57fbd5f9 DW |
670 | } else if ($action == 'viewsubmitforgradingerror') { |
671 | $o .= $this->view_error_page(get_string('submitforgrading', 'assign'), $notices); | |
7323c473 JO |
672 | } else if ($action == 'fixrescalednullgrades') { |
673 | $o .= $this->view_fix_rescaled_null_grades(); | |
bbd0e548 DW |
674 | } else { |
675 | $o .= $this->view_submission_page(); | |
676 | } | |
677 | ||
678 | return $o; | |
679 | } | |
680 | ||
bbd0e548 | 681 | /** |
e5403f8c | 682 | * Add this instance to the database. |
bbd0e548 DW |
683 | * |
684 | * @param stdClass $formdata The data submitted from the form | |
685 | * @param bool $callplugins This is used to skip the plugin code | |
686 | * when upgrading an old assignment to a new one (the plugins get called manually) | |
687 | * @return mixed false if an error occurs or the int id of the new instance | |
688 | */ | |
689 | public function add_instance(stdClass $formdata, $callplugins) { | |
690 | global $DB; | |
8e1266bf | 691 | $adminconfig = $this->get_admin_config(); |
bbd0e548 DW |
692 | |
693 | $err = ''; | |
694 | ||
e5403f8c | 695 | // Add the database record. |
bbd0e548 DW |
696 | $update = new stdClass(); |
697 | $update->name = $formdata->name; | |
698 | $update->timemodified = time(); | |
699 | $update->timecreated = time(); | |
700 | $update->course = $formdata->course; | |
701 | $update->courseid = $formdata->course; | |
702 | $update->intro = $formdata->intro; | |
703 | $update->introformat = $formdata->introformat; | |
18de52db | 704 | $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription); |
bbd0e548 | 705 | $update->submissiondrafts = $formdata->submissiondrafts; |
94f26900 | 706 | $update->requiresubmissionstatement = $formdata->requiresubmissionstatement; |
bbd0e548 | 707 | $update->sendnotifications = $formdata->sendnotifications; |
75f87a57 | 708 | $update->sendlatenotifications = $formdata->sendlatenotifications; |
8e1266bf DW |
709 | $update->sendstudentnotifications = $adminconfig->sendstudentnotifications; |
710 | if (isset($formdata->sendstudentnotifications)) { | |
711 | $update->sendstudentnotifications = $formdata->sendstudentnotifications; | |
712 | } | |
b618f2d9 | 713 | $update->duedate = $formdata->duedate; |
9e795179 | 714 | $update->cutoffdate = $formdata->cutoffdate; |
3e1c0275 | 715 | $update->gradingduedate = $formdata->gradingduedate; |
bbd0e548 DW |
716 | $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate; |
717 | $update->grade = $formdata->grade; | |
694b11ab | 718 | $update->completionsubmit = !empty($formdata->completionsubmit); |
12a1a0da DW |
719 | $update->teamsubmission = $formdata->teamsubmission; |
720 | $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit; | |
1fba9cf6 DW |
721 | if (isset($formdata->teamsubmissiongroupingid)) { |
722 | $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid; | |
723 | } | |
b473171a | 724 | $update->blindmarking = $formdata->blindmarking; |
f14afd29 AW |
725 | if (isset($formdata->hidegrader)) { |
726 | $update->hidegrader = $formdata->hidegrader; | |
727 | } | |
e8490064 DW |
728 | $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE; |
729 | if (!empty($formdata->attemptreopenmethod)) { | |
730 | $update->attemptreopenmethod = $formdata->attemptreopenmethod; | |
731 | } | |
df211804 DW |
732 | if (!empty($formdata->maxattempts)) { |
733 | $update->maxattempts = $formdata->maxattempts; | |
734 | } | |
e528997a AH |
735 | if (isset($formdata->preventsubmissionnotingroup)) { |
736 | $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup; | |
737 | } | |
f8d107b3 DM |
738 | $update->markingworkflow = $formdata->markingworkflow; |
739 | $update->markingallocation = $formdata->markingallocation; | |
5a96486b DM |
740 | if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled. |
741 | $update->markingallocation = 0; | |
742 | } | |
12a1a0da | 743 | |
bbd0e548 DW |
744 | $returnid = $DB->insert_record('assign', $update); |
745 | $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST); | |
e5403f8c | 746 | // Cache the course record. |
bbd0e548 DW |
747 | $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST); |
748 | ||
7faf78cb HB |
749 | $this->save_intro_draft_files($formdata); |
750 | ||
bbd0e548 | 751 | if ($callplugins) { |
e5403f8c | 752 | // Call save_settings hook for submission plugins. |
bbd0e548 DW |
753 | foreach ($this->submissionplugins as $plugin) { |
754 | if (!$this->update_plugin_instance($plugin, $formdata)) { | |
755 | print_error($plugin->get_error()); | |
756 | return false; | |
757 | } | |
758 | } | |
759 | foreach ($this->feedbackplugins as $plugin) { | |
760 | if (!$this->update_plugin_instance($plugin, $formdata)) { | |
761 | print_error($plugin->get_error()); | |
762 | return false; | |
763 | } | |
764 | } | |
765 | ||
e5403f8c DW |
766 | // In the case of upgrades the coursemodule has not been set, |
767 | // so we need to wait before calling these two. | |
bbd0e548 | 768 | $this->update_calendar($formdata->coursemodule); |
9ffbdafc AG |
769 | if (!empty($formdata->completionexpected)) { |
770 | \core_completion\api::update_completion_date_event($formdata->coursemodule, 'assign', $this->instance, | |
771 | $formdata->completionexpected); | |
772 | } | |
bbd0e548 DW |
773 | $this->update_gradebook(false, $formdata->coursemodule); |
774 | ||
775 | } | |
776 | ||
777 | $update = new stdClass(); | |
778 | $update->id = $this->get_instance()->id; | |
779 | $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0; | |
780 | $DB->update_record('assign', $update); | |
781 | ||
782 | return $returnid; | |
783 | } | |
784 | ||
785 | /** | |
e5403f8c | 786 | * Delete all grades from the gradebook for this assignment. |
bbd0e548 DW |
787 | * |
788 | * @return bool | |
789 | */ | |
47f48152 | 790 | protected function delete_grades() { |
bbd0e548 DW |
791 | global $CFG; |
792 | ||
e5403f8c DW |
793 | $result = grade_update('mod/assign', |
794 | $this->get_course()->id, | |
795 | 'mod', | |
796 | 'assign', | |
797 | $this->get_instance()->id, | |
798 | 0, | |
799 | null, | |
800 | array('deleted'=>1)); | |
801 | return $result == GRADE_UPDATE_OK; | |
bbd0e548 DW |
802 | } |
803 | ||
804 | /** | |
e5403f8c | 805 | * Delete this instance from the database. |
bbd0e548 DW |
806 | * |
807 | * @return bool false if an error occurs | |
808 | */ | |
809 | public function delete_instance() { | |
810 | global $DB; | |
811 | $result = true; | |
812 | ||
813 | foreach ($this->submissionplugins as $plugin) { | |
814 | if (!$plugin->delete_instance()) { | |
815 | print_error($plugin->get_error()); | |
816 | $result = false; | |
817 | } | |
818 | } | |
819 | foreach ($this->feedbackplugins as $plugin) { | |
820 | if (!$plugin->delete_instance()) { | |
821 | print_error($plugin->get_error()); | |
822 | $result = false; | |
823 | } | |
824 | } | |
825 | ||
e5403f8c | 826 | // Delete files associated with this assignment. |
bbd0e548 DW |
827 | $fs = get_file_storage(); |
828 | if (! $fs->delete_area_files($this->context->id) ) { | |
829 | $result = false; | |
830 | } | |
831 | ||
d685b959 IT |
832 | $this->delete_all_overrides(); |
833 | ||
e5403f8c | 834 | // Delete_records will throw an exception if it fails - so no need for error checking here. |
26ac0abc MN |
835 | $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id)); |
836 | $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id)); | |
837 | $DB->delete_records('assign_plugin_config', array('assignment' => $this->get_instance()->id)); | |
838 | $DB->delete_records('assign_user_flags', array('assignment' => $this->get_instance()->id)); | |
839 | $DB->delete_records('assign_user_mapping', array('assignment' => $this->get_instance()->id)); | |
bbd0e548 | 840 | |
e5403f8c | 841 | // Delete items from the gradebook. |
bbd0e548 DW |
842 | if (! $this->delete_grades()) { |
843 | $result = false; | |
844 | } | |
845 | ||
e5403f8c | 846 | // Delete the instance. |
40fe1538 | 847 | // We must delete the module record after we delete the grade item. |
bbd0e548 DW |
848 | $DB->delete_records('assign', array('id'=>$this->get_instance()->id)); |
849 | ||
850 | return $result; | |
851 | } | |
852 | ||
d685b959 IT |
853 | /** |
854 | * Deletes a assign override from the database and clears any corresponding calendar events | |
855 | * | |
856 | * @param int $overrideid The id of the override being deleted | |
857 | * @return bool true on success | |
858 | */ | |
859 | public function delete_override($overrideid) { | |
860 | global $CFG, $DB; | |
861 | ||
862 | require_once($CFG->dirroot . '/calendar/lib.php'); | |
863 | ||
f3ec5411 JP |
864 | $cm = $this->get_course_module(); |
865 | if (empty($cm)) { | |
866 | $instance = $this->get_instance(); | |
867 | $cm = get_coursemodule_from_instance('assign', $instance->id, $instance->course); | |
868 | } | |
d685b959 IT |
869 | |
870 | $override = $DB->get_record('assign_overrides', array('id' => $overrideid), '*', MUST_EXIST); | |
871 | ||
872 | // Delete the events. | |
f3ec5411 | 873 | $conds = array('modulename' => 'assign', 'instance' => $this->get_instance()->id); |
d685b959 IT |
874 | if (isset($override->userid)) { |
875 | $conds['userid'] = $override->userid; | |
876 | } else { | |
877 | $conds['groupid'] = $override->groupid; | |
878 | } | |
879 | $events = $DB->get_records('event', $conds); | |
880 | foreach ($events as $event) { | |
881 | $eventold = calendar_event::load($event); | |
882 | $eventold->delete(); | |
883 | } | |
884 | ||
885 | $DB->delete_records('assign_overrides', array('id' => $overrideid)); | |
886 | ||
887 | // Set the common parameters for one of the events we will be triggering. | |
888 | $params = array( | |
889 | 'objectid' => $override->id, | |
890 | 'context' => context_module::instance($cm->id), | |
891 | 'other' => array( | |
892 | 'assignid' => $override->assignid | |
893 | ) | |
894 | ); | |
895 | // Determine which override deleted event to fire. | |
896 | if (!empty($override->userid)) { | |
897 | $params['relateduserid'] = $override->userid; | |
898 | $event = \mod_assign\event\user_override_deleted::create($params); | |
899 | } else { | |
900 | $params['other']['groupid'] = $override->groupid; | |
901 | $event = \mod_assign\event\group_override_deleted::create($params); | |
902 | } | |
903 | ||
904 | // Trigger the override deleted event. | |
905 | $event->add_record_snapshot('assign_overrides', $override); | |
906 | $event->trigger(); | |
907 | ||
908 | return true; | |
909 | } | |
910 | ||
911 | /** | |
912 | * Deletes all assign overrides from the database and clears any corresponding calendar events | |
913 | */ | |
914 | public function delete_all_overrides() { | |
915 | global $DB; | |
916 | ||
f3ec5411 | 917 | $overrides = $DB->get_records('assign_overrides', array('assignid' => $this->get_instance()->id), 'id'); |
d685b959 IT |
918 | foreach ($overrides as $override) { |
919 | $this->delete_override($override->id); | |
920 | } | |
921 | } | |
922 | ||
923 | /** | |
924 | * Updates the assign properties with override information for a user. | |
925 | * | |
926 | * Algorithm: For each assign setting, if there is a matching user-specific override, | |
927 | * then use that otherwise, if there are group-specific overrides, return the most | |
928 | * lenient combination of them. If neither applies, leave the assign setting unchanged. | |
929 | * | |
930 | * @param int $userid The userid. | |
931 | */ | |
932 | public function update_effective_access($userid) { | |
933 | ||
934 | $override = $this->override_exists($userid); | |
935 | ||
936 | // Merge with assign defaults. | |
937 | $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); | |
938 | foreach ($keys as $key) { | |
939 | if (isset($override->{$key})) { | |
c872ec0c | 940 | $this->get_instance($userid)->{$key} = $override->{$key}; |
d685b959 IT |
941 | } |
942 | } | |
943 | ||
944 | } | |
945 | ||
a13f1f49 KH |
946 | /** |
947 | * Returns whether an assign has any overrides. | |
948 | * | |
949 | * @return true if any, false if not | |
950 | */ | |
951 | public function has_overrides() { | |
952 | global $DB; | |
953 | ||
954 | $override = $DB->record_exists('assign_overrides', array('assignid' => $this->get_instance()->id)); | |
955 | ||
956 | if ($override) { | |
957 | return true; | |
958 | } | |
959 | ||
960 | return false; | |
961 | } | |
962 | ||
d685b959 IT |
963 | /** |
964 | * Returns user override | |
965 | * | |
966 | * Algorithm: For each assign setting, if there is a matching user-specific override, | |
26aedd4a CB |
967 | * then use that otherwise, if there are group-specific overrides, use the one with the |
968 | * lowest sort order. If neither applies, leave the assign setting unchanged. | |
d685b959 IT |
969 | * |
970 | * @param int $userid The userid. | |
26aedd4a | 971 | * @return stdClass The override |
d685b959 IT |
972 | */ |
973 | public function override_exists($userid) { | |
974 | global $DB; | |
975 | ||
26aedd4a CB |
976 | // Gets an assoc array containing the keys for defined user overrides only. |
977 | $getuseroverride = function($userid) use ($DB) { | |
978 | $useroverride = $DB->get_record('assign_overrides', ['assignid' => $this->get_instance()->id, 'userid' => $userid]); | |
979 | return $useroverride ? get_object_vars($useroverride) : []; | |
980 | }; | |
d685b959 | 981 | |
26aedd4a CB |
982 | // Gets an assoc array containing the keys for defined group overrides only. |
983 | $getgroupoverride = function($userid) use ($DB) { | |
984 | $groupings = groups_get_user_groups($this->get_instance()->course, $userid); | |
d685b959 | 985 | |
26aedd4a CB |
986 | if (empty($groupings[0])) { |
987 | return []; | |
988 | } | |
d685b959 | 989 | |
d685b959 IT |
990 | // Select all overrides that apply to the User's groups. |
991 | list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0])); | |
992 | $sql = "SELECT * FROM {assign_overrides} | |
26aedd4a | 993 | WHERE groupid $extra AND assignid = ? ORDER BY sortorder ASC"; |
d685b959 | 994 | $params[] = $this->get_instance()->id; |
26aedd4a CB |
995 | $groupoverride = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE); |
996 | ||
997 | return $groupoverride ? get_object_vars($groupoverride) : []; | |
998 | }; | |
999 | ||
1000 | // Later arguments clobber earlier ones with array_merge. The two helper functions | |
1001 | // return arrays containing keys for only the defined overrides. So we get the | |
1002 | // desired behaviour as per the algorithm. | |
1003 | return (object)array_merge( | |
1004 | ['duedate' => null, 'cutoffdate' => null, 'allowsubmissionsfromdate' => null], | |
1005 | $getgroupoverride($userid), | |
1006 | $getuseroverride($userid) | |
1007 | ); | |
d685b959 IT |
1008 | } |
1009 | ||
f4c21561 RW |
1010 | /** |
1011 | * Check if the given calendar_event is either a user or group override | |
1012 | * event. | |
1013 | * | |
1014 | * @return bool | |
1015 | */ | |
1016 | public function is_override_calendar_event(\calendar_event $event) { | |
1017 | global $DB; | |
1018 | ||
1019 | if (!isset($event->modulename)) { | |
1020 | return false; | |
1021 | } | |
1022 | ||
1023 | if ($event->modulename != 'assign') { | |
1024 | return false; | |
1025 | } | |
1026 | ||
1027 | if (!isset($event->instance)) { | |
1028 | return false; | |
1029 | } | |
1030 | ||
1031 | if (!isset($event->userid) && !isset($event->groupid)) { | |
1032 | return false; | |
1033 | } | |
1034 | ||
1035 | $overrideparams = [ | |
1036 | 'assignid' => $event->instance | |
1037 | ]; | |
1038 | ||
1039 | if (isset($event->groupid)) { | |
1040 | $overrideparams['groupid'] = $event->groupid; | |
1041 | } else if (isset($event->userid)) { | |
1042 | $overrideparams['userid'] = $event->userid; | |
1043 | } | |
1044 | ||
1045 | if ($DB->get_record('assign_overrides', $overrideparams)) { | |
1046 | return true; | |
1047 | } else { | |
1048 | return false; | |
1049 | } | |
1050 | } | |
1051 | ||
1052 | /** | |
1053 | * This function calculates the minimum and maximum cutoff values for the timestart of | |
1054 | * the given event. | |
1055 | * | |
1056 | * It will return an array with two values, the first being the minimum cutoff value and | |
1057 | * the second being the maximum cutoff value. Either or both values can be null, which | |
1058 | * indicates there is no minimum or maximum, respectively. | |
1059 | * | |
1060 | * If a cutoff is required then the function must return an array containing the cutoff | |
1061 | * timestamp and error string to display to the user if the cutoff value is violated. | |
1062 | * | |
1063 | * A minimum and maximum cutoff return value will look like: | |
1064 | * [ | |
1065 | * [1505704373, 'The due date must be after the sbumission start date'], | |
1066 | * [1506741172, 'The due date must be before the cutoff date'] | |
1067 | * ] | |
1068 | * | |
6688ae2b RW |
1069 | * If the event does not have a valid timestart range then [false, false] will |
1070 | * be returned. | |
1071 | * | |
f4c21561 | 1072 | * @param calendar_event $event The calendar event to get the time range for |
6688ae2b | 1073 | * @return array |
f4c21561 RW |
1074 | */ |
1075 | function get_valid_calendar_event_timestart_range(\calendar_event $event) { | |
1076 | $instance = $this->get_instance(); | |
1077 | $submissionsfromdate = $instance->allowsubmissionsfromdate; | |
1078 | $cutoffdate = $instance->cutoffdate; | |
1079 | $duedate = $instance->duedate; | |
1080 | $gradingduedate = $instance->gradingduedate; | |
1081 | $mindate = null; | |
1082 | $maxdate = null; | |
1083 | ||
1084 | if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) { | |
1085 | // This check is in here because due date events are currently | |
1086 | // the only events that can be overridden, so we can save a DB | |
1087 | // query if we don't bother checking other events. | |
1088 | if ($this->is_override_calendar_event($event)) { | |
6688ae2b RW |
1089 | // This is an override event so there is no valid timestart |
1090 | // range to set it to. | |
1091 | return [false, false]; | |
f4c21561 RW |
1092 | } |
1093 | ||
1094 | if ($submissionsfromdate) { | |
1095 | $mindate = [ | |
1096 | $submissionsfromdate, | |
1097 | get_string('duedatevalidation', 'assign'), | |
1098 | ]; | |
1099 | } | |
1100 | ||
1101 | if ($cutoffdate) { | |
1102 | $maxdate = [ | |
1103 | $cutoffdate, | |
1104 | get_string('cutoffdatevalidation', 'assign'), | |
1105 | ]; | |
1106 | } | |
1107 | ||
1108 | if ($gradingduedate) { | |
1109 | // If we don't have a cutoff date or we've got a grading due date | |
1110 | // that is earlier than the cutoff then we should use that as the | |
1111 | // upper limit for the due date. | |
1112 | if (!$cutoffdate || $gradingduedate < $cutoffdate) { | |
1113 | $maxdate = [ | |
1114 | $gradingduedate, | |
1115 | get_string('gradingdueduedatevalidation', 'assign'), | |
1116 | ]; | |
1117 | } | |
1118 | } | |
1119 | } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { | |
1120 | if ($duedate) { | |
1121 | $mindate = [ | |
1122 | $duedate, | |
1123 | get_string('gradingdueduedatevalidation', 'assign'), | |
1124 | ]; | |
1125 | } else if ($submissionsfromdate) { | |
1126 | $mindate = [ | |
1127 | $submissionsfromdate, | |
1128 | get_string('gradingduefromdatevalidation', 'assign'), | |
1129 | ]; | |
1130 | } | |
1131 | } | |
1132 | ||
1133 | return [$mindate, $maxdate]; | |
1134 | } | |
1135 | ||
d38dc52f | 1136 | /** |
e5403f8c DW |
1137 | * Actual implementation of the reset course functionality, delete all the |
1138 | * assignment submissions for course $data->courseid. | |
1139 | * | |
1561a37c | 1140 | * @param stdClass $data the data submitted from the reset course. |
e5403f8c DW |
1141 | * @return array status array |
1142 | */ | |
d38dc52f | 1143 | public function reset_userdata($data) { |
e5403f8c | 1144 | global $CFG, $DB; |
d38dc52f RW |
1145 | |
1146 | $componentstr = get_string('modulenameplural', 'assign'); | |
1147 | $status = array(); | |
1148 | ||
1149 | $fs = get_file_storage(); | |
1150 | if (!empty($data->reset_assign_submissions)) { | |
1151 | // Delete files associated with this assignment. | |
1152 | foreach ($this->submissionplugins as $plugin) { | |
1153 | $fileareas = array(); | |
1154 | $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type(); | |
1155 | $fileareas = $plugin->get_file_areas(); | |
43fb9ced | 1156 | foreach ($fileareas as $filearea => $notused) { |
d38dc52f RW |
1157 | $fs->delete_area_files($this->context->id, $plugincomponent, $filearea); |
1158 | } | |
1159 | ||
1160 | if (!$plugin->delete_instance()) { | |
1161 | $status[] = array('component'=>$componentstr, | |
e5403f8c | 1162 | 'item'=>get_string('deleteallsubmissions', 'assign'), |
d38dc52f RW |
1163 | 'error'=>$plugin->get_error()); |
1164 | } | |
1165 | } | |
1166 | ||
1167 | foreach ($this->feedbackplugins as $plugin) { | |
1168 | $fileareas = array(); | |
1169 | $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type(); | |
1170 | $fileareas = $plugin->get_file_areas(); | |
43fb9ced | 1171 | foreach ($fileareas as $filearea => $notused) { |
d38dc52f RW |
1172 | $fs->delete_area_files($this->context->id, $plugincomponent, $filearea); |
1173 | } | |
1174 | ||
1175 | if (!$plugin->delete_instance()) { | |
1176 | $status[] = array('component'=>$componentstr, | |
e5403f8c | 1177 | 'item'=>get_string('deleteallsubmissions', 'assign'), |
d38dc52f RW |
1178 | 'error'=>$plugin->get_error()); |
1179 | } | |
1180 | } | |
1181 | ||
26ac0abc MN |
1182 | $assignids = $DB->get_records('assign', array('course' => $data->courseid), '', 'id'); |
1183 | list($sql, $params) = $DB->get_in_or_equal(array_keys($assignids)); | |
d38dc52f | 1184 | |
26ac0abc MN |
1185 | $DB->delete_records_select('assign_submission', "assignment $sql", $params); |
1186 | $DB->delete_records_select('assign_user_flags', "assignment $sql", $params); | |
e5403f8c | 1187 | |
d38dc52f | 1188 | $status[] = array('component'=>$componentstr, |
e5403f8c | 1189 | 'item'=>get_string('deleteallsubmissions', 'assign'), |
d38dc52f RW |
1190 | 'error'=>false); |
1191 | ||
e5403f8c | 1192 | if (!empty($data->reset_gradebook_grades)) { |
26ac0abc | 1193 | $DB->delete_records_select('assign_grades', "assignment $sql", $params); |
d38dc52f RW |
1194 | // Remove all grades from gradebook. |
1195 | require_once($CFG->dirroot.'/mod/assign/lib.php'); | |
1196 | assign_reset_gradebook($data->courseid); | |
1a9e8542 | 1197 | } |
48242bf3 | 1198 | |
1a9e8542 ZT |
1199 | // Reset revealidentities for assign if blindmarking is enabled. |
1200 | if ($this->get_instance()->blindmarking) { | |
1201 | $DB->set_field('assign', 'revealidentities', 0, array('id' => $this->get_instance()->id)); | |
d38dc52f RW |
1202 | } |
1203 | } | |
d685b959 IT |
1204 | |
1205 | // Remove user overrides. | |
1206 | if (!empty($data->reset_assign_user_overrides)) { | |
1207 | $DB->delete_records_select('assign_overrides', | |
1208 | 'assignid IN (SELECT id FROM {assign} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid)); | |
1209 | $status[] = array( | |
1210 | 'component' => $componentstr, | |
1211 | 'item' => get_string('useroverridesdeleted', 'assign'), | |
1212 | 'error' => false); | |
1213 | } | |
1214 | // Remove group overrides. | |
1215 | if (!empty($data->reset_assign_group_overrides)) { | |
1216 | $DB->delete_records_select('assign_overrides', | |
1217 | 'assignid IN (SELECT id FROM {assign} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid)); | |
1218 | $status[] = array( | |
1219 | 'component' => $componentstr, | |
1220 | 'item' => get_string('groupoverridesdeleted', 'assign'), | |
1221 | 'error' => false); | |
1222 | } | |
1223 | ||
d38dc52f RW |
1224 | // Updating dates - shift may be negative too. |
1225 | if ($data->timeshift) { | |
d685b959 IT |
1226 | $DB->execute("UPDATE {assign_overrides} |
1227 | SET allowsubmissionsfromdate = allowsubmissionsfromdate + ? | |
1228 | WHERE assignid = ? AND allowsubmissionsfromdate <> 0", | |
1229 | array($data->timeshift, $this->get_instance()->id)); | |
1230 | $DB->execute("UPDATE {assign_overrides} | |
1231 | SET duedate = duedate + ? | |
1232 | WHERE assignid = ? AND duedate <> 0", | |
1233 | array($data->timeshift, $this->get_instance()->id)); | |
1234 | $DB->execute("UPDATE {assign_overrides} | |
1235 | SET cutoffdate = cutoffdate + ? | |
1236 | WHERE assignid =? AND cutoffdate <> 0", | |
1237 | array($data->timeshift, $this->get_instance()->id)); | |
1238 | ||
0d14fcbc AA |
1239 | // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. |
1240 | // See MDL-9367. | |
d38dc52f | 1241 | shift_course_mod_dates('assign', |
e5403f8c | 1242 | array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'), |
d38dc52f | 1243 | $data->timeshift, |
e63515ba | 1244 | $data->courseid, $this->get_instance()->id); |
d38dc52f RW |
1245 | $status[] = array('component'=>$componentstr, |
1246 | 'item'=>get_string('datechanged'), | |
1247 | 'error'=>false); | |
1248 | } | |
1249 | ||
1250 | return $status; | |
1251 | } | |
1252 | ||
bbd0e548 | 1253 | /** |
e5403f8c | 1254 | * Update the settings for a single plugin. |
bbd0e548 DW |
1255 | * |
1256 | * @param assign_plugin $plugin The plugin to update | |
1257 | * @param stdClass $formdata The form data | |
1258 | * @return bool false if an error occurs | |
1259 | */ | |
47f48152 | 1260 | protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) { |
bbd0e548 DW |
1261 | if ($plugin->is_visible()) { |
1262 | $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled'; | |
b0da618b | 1263 | if (!empty($formdata->$enabledname)) { |
bbd0e548 DW |
1264 | $plugin->enable(); |
1265 | if (!$plugin->save_settings($formdata)) { | |
1266 | print_error($plugin->get_error()); | |
1267 | return false; | |
1268 | } | |
1269 | } else { | |
1270 | $plugin->disable(); | |
1271 | } | |
1272 | } | |
1273 | return true; | |
1274 | } | |
1275 | ||
1276 | /** | |
e5403f8c | 1277 | * Update the gradebook information for this assignment. |
bbd0e548 DW |
1278 | * |
1279 | * @param bool $reset If true, will reset all grades in the gradbook for this assignment | |
1280 | * @param int $coursemoduleid This is required because it might not exist in the database yet | |
1281 | * @return bool | |
1282 | */ | |
1283 | public function update_gradebook($reset, $coursemoduleid) { | |
e5403f8c DW |
1284 | global $CFG; |
1285 | ||
bbd0e548 DW |
1286 | require_once($CFG->dirroot.'/mod/assign/lib.php'); |
1287 | $assign = clone $this->get_instance(); | |
1288 | $assign->cmidnumber = $coursemoduleid; | |
456d7bc7 RT |
1289 | |
1290 | // Set assign gradebook feedback plugin status (enabled and visible). | |
1291 | $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled(); | |
1292 | ||
bbd0e548 DW |
1293 | $param = null; |
1294 | if ($reset) { | |
1295 | $param = 'reset'; | |
1296 | } | |
1297 | ||
1298 | return assign_grade_item_update($assign, $param); | |
1299 | } | |
1300 | ||
80989850 BH |
1301 | /** |
1302 | * Get the marking table page size | |
1303 | * | |
1304 | * @return integer | |
1305 | */ | |
1306 | public function get_assign_perpage() { | |
1307 | $perpage = (int) get_user_preferences('assign_perpage', 10); | |
1308 | $adminconfig = $this->get_admin_config(); | |
1309 | $maxperpage = -1; | |
1310 | if (isset($adminconfig->maxperpage)) { | |
1311 | $maxperpage = $adminconfig->maxperpage; | |
1312 | } | |
1313 | if (isset($maxperpage) && | |
1314 | $maxperpage != -1 && | |
1315 | ($perpage == -1 || $perpage > $maxperpage)) { | |
1316 | $perpage = $maxperpage; | |
1317 | } | |
1318 | return $perpage; | |
1319 | } | |
1320 | ||
e5403f8c DW |
1321 | /** |
1322 | * Load and cache the admin config for this module. | |
bc5a657b | 1323 | * |
cfc81f03 DW |
1324 | * @return stdClass the plugin config |
1325 | */ | |
1326 | public function get_admin_config() { | |
1327 | if ($this->adminconfig) { | |
1328 | return $this->adminconfig; | |
1329 | } | |
b11808c7 | 1330 | $this->adminconfig = get_config('assign'); |
cfc81f03 DW |
1331 | return $this->adminconfig; |
1332 | } | |
1333 | ||
bbd0e548 | 1334 | /** |
e5403f8c | 1335 | * Update the calendar entries for this assignment. |
bbd0e548 | 1336 | * |
e5403f8c DW |
1337 | * @param int $coursemoduleid - Required to pass this in because it might |
1338 | * not exist in the database yet. | |
bbd0e548 DW |
1339 | * @return bool |
1340 | */ | |
1341 | public function update_calendar($coursemoduleid) { | |
1342 | global $DB, $CFG; | |
1343 | require_once($CFG->dirroot.'/calendar/lib.php'); | |
1344 | ||
e5403f8c DW |
1345 | // Special case for add_instance as the coursemodule has not been set yet. |
1346 | $instance = $this->get_instance(); | |
bbd0e548 | 1347 | |
294dce67 MN |
1348 | // Start with creating the event. |
1349 | $event = new stdClass(); | |
1350 | $event->modulename = 'assign'; | |
1351 | $event->courseid = $instance->course; | |
1352 | $event->groupid = 0; | |
1353 | $event->userid = 0; | |
1354 | $event->instance = $instance->id; | |
294dce67 MN |
1355 | $event->type = CALENDAR_EVENT_TYPE_ACTION; |
1356 | ||
1357 | // Convert the links to pluginfile. It is a bit hacky but at this stage the files | |
1358 | // might not have been saved in the module area yet. | |
1359 | $intro = $instance->intro; | |
1360 | if ($draftid = file_get_submitted_draft_itemid('introeditor')) { | |
1361 | $intro = file_rewrite_urls_to_pluginfile($intro, $draftid); | |
1362 | } | |
1363 | ||
1364 | // We need to remove the links to files as the calendar is not ready | |
1365 | // to support module events with file areas. | |
1366 | $intro = strip_pluginfile_content($intro); | |
1367 | if ($this->show_intro()) { | |
1368 | $event->description = array( | |
1369 | 'text' => $intro, | |
1370 | 'format' => $instance->introformat | |
1371 | ); | |
1372 | } else { | |
1373 | $event->description = array( | |
1374 | 'text' => '', | |
1375 | 'format' => $instance->introformat | |
1376 | ); | |
1377 | } | |
72797e68 | 1378 | |
294dce67 | 1379 | $eventtype = ASSIGN_EVENT_TYPE_DUE; |
e5403f8c | 1380 | if ($instance->duedate) { |
f10721d6 | 1381 | $event->name = get_string('calendardue', 'assign', $instance->name); |
294dce67 MN |
1382 | $event->eventtype = $eventtype; |
1383 | $event->timestart = $instance->duedate; | |
1384 | $event->timesort = $instance->duedate; | |
98239b2d JP |
1385 | $select = "modulename = :modulename |
1386 | AND instance = :instance | |
1387 | AND eventtype = :eventtype | |
1388 | AND groupid = 0 | |
1389 | AND courseid <> 0"; | |
72797e68 | 1390 | $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype); |
98239b2d | 1391 | $event->id = $DB->get_field_select('event', 'id', $select, $params); |
0cfc7d13 | 1392 | |
294dce67 MN |
1393 | // Now process the event. |
1394 | if ($event->id) { | |
e1cd93ce | 1395 | $calendarevent = calendar_event::load($event->id); |
bbf1fde4 | 1396 | $calendarevent->update($event, false); |
ae7638f7 | 1397 | } else { |
bbf1fde4 | 1398 | calendar_event::create($event, false); |
ae7638f7 | 1399 | } |
294dce67 MN |
1400 | } else { |
1401 | $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, | |
1402 | 'eventtype' => $eventtype)); | |
1403 | } | |
bbd0e548 | 1404 | |
294dce67 MN |
1405 | $eventtype = ASSIGN_EVENT_TYPE_GRADINGDUE; |
1406 | if ($instance->gradingduedate) { | |
f10721d6 | 1407 | $event->name = get_string('calendargradingdue', 'assign', $instance->name); |
294dce67 MN |
1408 | $event->eventtype = $eventtype; |
1409 | $event->timestart = $instance->gradingduedate; | |
1410 | $event->timesort = $instance->gradingduedate; | |
1411 | $event->id = $DB->get_field('event', 'id', array('modulename' => 'assign', | |
1412 | 'instance' => $instance->id, 'eventtype' => $event->eventtype)); | |
1413 | ||
1414 | // Now process the event. | |
0cfc7d13 | 1415 | if ($event->id) { |
bbd0e548 | 1416 | $calendarevent = calendar_event::load($event->id); |
bbf1fde4 | 1417 | $calendarevent->update($event, false); |
bbd0e548 | 1418 | } else { |
bbf1fde4 | 1419 | calendar_event::create($event, false); |
bbd0e548 DW |
1420 | } |
1421 | } else { | |
294dce67 MN |
1422 | $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, |
1423 | 'eventtype' => $eventtype)); | |
bbd0e548 | 1424 | } |
bbd0e548 | 1425 | |
294dce67 MN |
1426 | return true; |
1427 | } | |
bbd0e548 DW |
1428 | |
1429 | /** | |
e5403f8c | 1430 | * Update this instance in the database. |
bbd0e548 DW |
1431 | * |
1432 | * @param stdClass $formdata - the data submitted from the form | |
1433 | * @return bool false if an error occurs | |
1434 | */ | |
1435 | public function update_instance($formdata) { | |
1436 | global $DB; | |
8e1266bf | 1437 | $adminconfig = $this->get_admin_config(); |
bbd0e548 DW |
1438 | |
1439 | $update = new stdClass(); | |
1440 | $update->id = $formdata->instance; | |
1441 | $update->name = $formdata->name; | |
1442 | $update->timemodified = time(); | |
1443 | $update->course = $formdata->course; | |
1444 | $update->intro = $formdata->intro; | |
1445 | $update->introformat = $formdata->introformat; | |
18de52db | 1446 | $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription); |
bbd0e548 | 1447 | $update->submissiondrafts = $formdata->submissiondrafts; |
94f26900 | 1448 | $update->requiresubmissionstatement = $formdata->requiresubmissionstatement; |
bbd0e548 | 1449 | $update->sendnotifications = $formdata->sendnotifications; |
75f87a57 | 1450 | $update->sendlatenotifications = $formdata->sendlatenotifications; |
8e1266bf DW |
1451 | $update->sendstudentnotifications = $adminconfig->sendstudentnotifications; |
1452 | if (isset($formdata->sendstudentnotifications)) { | |
1453 | $update->sendstudentnotifications = $formdata->sendstudentnotifications; | |
1454 | } | |
b618f2d9 | 1455 | $update->duedate = $formdata->duedate; |
9e795179 | 1456 | $update->cutoffdate = $formdata->cutoffdate; |
3e1c0275 | 1457 | $update->gradingduedate = $formdata->gradingduedate; |
bbd0e548 DW |
1458 | $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate; |
1459 | $update->grade = $formdata->grade; | |
a9f5fc15 | 1460 | if (!empty($formdata->completionunlocked)) { |
1461 | $update->completionsubmit = !empty($formdata->completionsubmit); | |
1462 | } | |
12a1a0da DW |
1463 | $update->teamsubmission = $formdata->teamsubmission; |
1464 | $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit; | |
1fba9cf6 DW |
1465 | if (isset($formdata->teamsubmissiongroupingid)) { |
1466 | $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid; | |
1467 | } | |
f14afd29 AW |
1468 | if (isset($formdata->hidegrader)) { |
1469 | $update->hidegrader = $formdata->hidegrader; | |
1470 | } | |
b473171a | 1471 | $update->blindmarking = $formdata->blindmarking; |
e8490064 DW |
1472 | $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE; |
1473 | if (!empty($formdata->attemptreopenmethod)) { | |
1474 | $update->attemptreopenmethod = $formdata->attemptreopenmethod; | |
1475 | } | |
df211804 DW |
1476 | if (!empty($formdata->maxattempts)) { |
1477 | $update->maxattempts = $formdata->maxattempts; | |
1478 | } | |
e528997a AH |
1479 | if (isset($formdata->preventsubmissionnotingroup)) { |
1480 | $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup; | |
1481 | } | |
f8d107b3 DM |
1482 | $update->markingworkflow = $formdata->markingworkflow; |
1483 | $update->markingallocation = $formdata->markingallocation; | |
5a96486b DM |
1484 | if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled. |
1485 | $update->markingallocation = 0; | |
1486 | } | |
12a1a0da | 1487 | |
bbd0e548 DW |
1488 | $result = $DB->update_record('assign', $update); |
1489 | $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST); | |
1490 | ||
7faf78cb HB |
1491 | $this->save_intro_draft_files($formdata); |
1492 | ||
e5403f8c | 1493 | // Load the assignment so the plugins have access to it. |
bbd0e548 | 1494 | |
e5403f8c | 1495 | // Call save_settings hook for submission plugins. |
bbd0e548 DW |
1496 | foreach ($this->submissionplugins as $plugin) { |
1497 | if (!$this->update_plugin_instance($plugin, $formdata)) { | |
1498 | print_error($plugin->get_error()); | |
1499 | return false; | |
1500 | } | |
1501 | } | |
1502 | foreach ($this->feedbackplugins as $plugin) { | |
1503 | if (!$this->update_plugin_instance($plugin, $formdata)) { | |
1504 | print_error($plugin->get_error()); | |
1505 | return false; | |
1506 | } | |
1507 | } | |
1508 | ||
bbd0e548 | 1509 | $this->update_calendar($this->get_course_module()->id); |
9ffbdafc AG |
1510 | $completionexpected = (!empty($formdata->completionexpected)) ? $formdata->completionexpected : null; |
1511 | \core_completion\api::update_completion_date_event($this->get_course_module()->id, 'assign', $this->instance, | |
1512 | $completionexpected); | |
bbd0e548 DW |
1513 | $this->update_gradebook(false, $this->get_course_module()->id); |
1514 | ||
1515 | $update = new stdClass(); | |
1516 | $update->id = $this->get_instance()->id; | |
1517 | $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0; | |
1518 | $DB->update_record('assign', $update); | |
1519 | ||
bbd0e548 DW |
1520 | return $result; |
1521 | } | |
1522 | ||
7faf78cb HB |
1523 | /** |
1524 | * Save the attachments in the draft areas. | |
1525 | * | |
1526 | * @param stdClass $formdata | |
1527 | */ | |
1528 | protected function save_intro_draft_files($formdata) { | |
1529 | if (isset($formdata->introattachments)) { | |
1530 | file_save_draft_area_files($formdata->introattachments, $this->get_context()->id, | |
1531 | 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0); | |
1532 | } | |
1533 | } | |
1534 | ||
bbd0e548 | 1535 | /** |
e5403f8c | 1536 | * Add elements in grading plugin form. |
bbd0e548 DW |
1537 | * |
1538 | * @param mixed $grade stdClass|null | |
1539 | * @param MoodleQuickForm $mform | |
1540 | * @param stdClass $data | |
fc7b7d52 | 1541 | * @param int $userid - The userid we are grading |
bbd0e548 DW |
1542 | * @return void |
1543 | */ | |
47f48152 | 1544 | protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) { |
bbd0e548 DW |
1545 | foreach ($this->feedbackplugins as $plugin) { |
1546 | if ($plugin->is_enabled() && $plugin->is_visible()) { | |
3a5c3b3c | 1547 | $plugin->get_form_elements_for_user($grade, $mform, $data, $userid); |
bbd0e548 DW |
1548 | } |
1549 | } | |
1550 | } | |
1551 | ||
1552 | ||
1553 | ||
1554 | /** | |
e5403f8c | 1555 | * Add one plugins settings to edit plugin form. |
bbd0e548 DW |
1556 | * |
1557 | * @param assign_plugin $plugin The plugin to add the settings from | |
e5403f8c DW |
1558 | * @param MoodleQuickForm $mform The form to add the configuration settings to. |
1559 | * This form is modified directly (not returned). | |
b0da618b DW |
1560 | * @param array $pluginsenabled A list of form elements to be added to a group. |
1561 | * The new element is added to this array by this function. | |
bbd0e548 DW |
1562 | * @return void |
1563 | */ | |
b0da618b | 1564 | protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) { |
bbd0e548 | 1565 | global $CFG; |
f159ad73 | 1566 | if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) { |
1567 | $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled'; | |
1568 | $pluginsenabled[] = $mform->createElement('hidden', $name, 1); | |
1569 | $mform->setType($name, PARAM_BOOL); | |
1570 | $plugin->get_settings($mform); | |
1571 | } else if ($plugin->is_visible() && $plugin->is_configurable()) { | |
b0da618b DW |
1572 | $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled'; |
1573 | $label = $plugin->get_name(); | |
b0da618b | 1574 | $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label); |
9ed5b0c1 TB |
1575 | $helpicon = $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type()); |
1576 | $pluginsenabled[] = $mform->createElement('static', '', '', $helpicon); | |
bbd0e548 | 1577 | |
cfc81f03 | 1578 | $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default'); |
bbd0e548 DW |
1579 | if ($plugin->get_config('enabled') !== false) { |
1580 | $default = $plugin->is_enabled(); | |
1581 | } | |
1582 | $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default); | |
1583 | ||
1584 | $plugin->get_settings($mform); | |
1585 | ||
1586 | } | |
bbd0e548 DW |
1587 | } |
1588 | ||
bbd0e548 | 1589 | /** |
e5403f8c | 1590 | * Add settings to edit plugin form. |
bbd0e548 | 1591 | * |
e5403f8c DW |
1592 | * @param MoodleQuickForm $mform The form to add the configuration settings to. |
1593 | * This form is modified directly (not returned). | |
bbd0e548 DW |
1594 | * @return void |
1595 | */ | |
1596 | public function add_all_plugin_settings(MoodleQuickForm $mform) { | |
df211804 | 1597 | $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign')); |
bbd0e548 | 1598 | |
b0da618b DW |
1599 | $submissionpluginsenabled = array(); |
1600 | $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false); | |
bbd0e548 | 1601 | foreach ($this->submissionplugins as $plugin) { |
b0da618b | 1602 | $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled); |
bbd0e548 | 1603 | } |
b0da618b DW |
1604 | $group->setElements($submissionpluginsenabled); |
1605 | ||
df211804 | 1606 | $mform->addElement('header', 'feedbacktypes', get_string('feedbacktypes', 'assign')); |
b0da618b DW |
1607 | $feedbackpluginsenabled = array(); |
1608 | $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false); | |
bbd0e548 | 1609 | foreach ($this->feedbackplugins as $plugin) { |
b0da618b | 1610 | $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled); |
bbd0e548 | 1611 | } |
b0da618b DW |
1612 | $group->setElements($feedbackpluginsenabled); |
1613 | $mform->setExpanded('submissiontypes'); | |
bbd0e548 DW |
1614 | } |
1615 | ||
1616 | /** | |
1617 | * Allow each plugin an opportunity to update the defaultvalues | |
1618 | * passed in to the settings form (needed to set up draft areas for | |
1619 | * editor and filemanager elements) | |
e5403f8c | 1620 | * |
bbd0e548 DW |
1621 | * @param array $defaultvalues |
1622 | */ | |
1623 | public function plugin_data_preprocessing(&$defaultvalues) { | |
1624 | foreach ($this->submissionplugins as $plugin) { | |
1625 | if ($plugin->is_visible()) { | |
1626 | $plugin->data_preprocessing($defaultvalues); | |
1627 | } | |
1628 | } | |
1629 | foreach ($this->feedbackplugins as $plugin) { | |
1630 | if ($plugin->is_visible()) { | |
1631 | $plugin->data_preprocessing($defaultvalues); | |
1632 | } | |
1633 | } | |
1634 | } | |
1635 | ||
1636 | /** | |
1637 | * Get the name of the current module. | |
1638 | * | |
1639 | * @return string the module name (Assignment) | |
1640 | */ | |
1641 | protected function get_module_name() { | |
1642 | if (isset(self::$modulename)) { | |
1643 | return self::$modulename; | |
1644 | } | |
1645 | self::$modulename = get_string('modulename', 'assign'); | |
1646 | return self::$modulename; | |
1647 | } | |
1648 | ||
1649 | /** | |
1650 | * Get the plural name of the current module. | |
1651 | * | |
1652 | * @return string the module name plural (Assignments) | |
1653 | */ | |
1654 | protected function get_module_name_plural() { | |
1655 | if (isset(self::$modulenameplural)) { | |
1656 | return self::$modulenameplural; | |
1657 | } | |
1658 | self::$modulenameplural = get_string('modulenameplural', 'assign'); | |
1659 | return self::$modulenameplural; | |
1660 | } | |
1661 | ||
1662 | /** | |
1663 | * Has this assignment been constructed from an instance? | |
1664 | * | |
1665 | * @return bool | |
1666 | */ | |
1667 | public function has_instance() { | |
1668 | return $this->instance || $this->get_course_module(); | |
1669 | } | |
1670 | ||
1671 | /** | |
db381d5c | 1672 | * Get the settings for the current instance of this assignment. |
bbd0e548 DW |
1673 | * |
1674 | * @return stdClass The settings | |
db381d5c | 1675 | * @throws dml_exception |
bbd0e548 | 1676 | */ |
db381d5c | 1677 | public function get_default_instance() { |
bbd0e548 | 1678 | global $DB; |
db381d5c | 1679 | if (!$this->instance && $this->get_course_module()) { |
e5403f8c DW |
1680 | $params = array('id' => $this->get_course_module()->instance); |
1681 | $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST); | |
db381d5c JD |
1682 | |
1683 | $this->userinstances = []; | |
bbd0e548 DW |
1684 | } |
1685 | return $this->instance; | |
1686 | } | |
1687 | ||
db381d5c JD |
1688 | /** |
1689 | * Get the settings for the current instance of this assignment | |
1690 | * @param int|null $userid the id of the user to load the assign instance for. | |
1691 | * @return stdClass The settings | |
1692 | */ | |
1693 | public function get_instance(int $userid = null) : stdClass { | |
1694 | global $USER; | |
1695 | $userid = $userid ?? $USER->id; | |
1696 | ||
1697 | $this->instance = $this->get_default_instance(); | |
1698 | ||
1699 | // If we have the user instance already, just return it. | |
1700 | if (isset($this->userinstances[$userid])) { | |
1701 | return $this->userinstances[$userid]; | |
1702 | } | |
1703 | ||
1704 | // Calculate properties which vary per user. | |
1705 | $this->userinstances[$userid] = $this->calculate_properties($this->instance, $userid); | |
1706 | return $this->userinstances[$userid]; | |
1707 | } | |
1708 | ||
1709 | /** | |
1710 | * Calculates and updates various properties based on the specified user. | |
1711 | * | |
1712 | * @param stdClass $record the raw assign record. | |
1713 | * @param int $userid the id of the user to calculate the properties for. | |
1714 | * @return stdClass a new record having calculated properties. | |
1715 | */ | |
1716 | private function calculate_properties(\stdClass $record, int $userid) : \stdClass { | |
1717 | $record = clone ($record); | |
1718 | ||
1719 | // Relative dates. | |
1720 | if (!empty($record->duedate)) { | |
1721 | $course = $this->get_course(); | |
1722 | $usercoursedates = course_get_course_dates_for_user_id($course, $userid); | |
1723 | if ($usercoursedates['start']) { | |
1724 | $userprops = ['duedate' => $record->duedate + $usercoursedates['startoffset']]; | |
1725 | $record = (object) array_merge((array) $record, (array) $userprops); | |
1726 | } | |
1727 | } | |
1728 | return $record; | |
1729 | } | |
1730 | ||
0d64c75f PN |
1731 | /** |
1732 | * Get the primary grade item for this assign instance. | |
1733 | * | |
db8514bc | 1734 | * @return grade_item The grade_item record |
0d64c75f PN |
1735 | */ |
1736 | public function get_grade_item() { | |
1737 | if ($this->gradeitem) { | |
1738 | return $this->gradeitem; | |
1739 | } | |
1740 | $instance = $this->get_instance(); | |
1741 | $params = array('itemtype' => 'mod', | |
1742 | 'itemmodule' => 'assign', | |
1743 | 'iteminstance' => $instance->id, | |
1744 | 'courseid' => $instance->course, | |
1745 | 'itemnumber' => 0); | |
1746 | $this->gradeitem = grade_item::fetch($params); | |
1747 | if (!$this->gradeitem) { | |
1748 | throw new coding_exception('Improper use of the assignment class. ' . | |
1749 | 'Cannot load the grade item.'); | |
1750 | } | |
1751 | return $this->gradeitem; | |
1752 | } | |
1753 | ||
bbd0e548 | 1754 | /** |
e5403f8c DW |
1755 | * Get the context of the current course. |
1756 | * | |
bbd0e548 DW |
1757 | * @return mixed context|null The course context |
1758 | */ | |
1759 | public function get_course_context() { | |
1760 | if (!$this->context && !$this->course) { | |
e5403f8c DW |
1761 | throw new coding_exception('Improper use of the assignment class. ' . |
1762 | 'Cannot load the course context.'); | |
bbd0e548 DW |
1763 | } |
1764 | if ($this->context) { | |
1765 | return $this->context->get_course_context(); | |
1766 | } else { | |
1767 | return context_course::instance($this->course->id); | |
1768 | } | |
1769 | } | |
1770 | ||
1771 | ||
1772 | /** | |
e5403f8c | 1773 | * Get the current course module. |
bbd0e548 | 1774 | * |
c13ac85d | 1775 | * @return cm_info|null The course module or null if not known |
bbd0e548 DW |
1776 | */ |
1777 | public function get_course_module() { | |
1778 | if ($this->coursemodule) { | |
1779 | return $this->coursemodule; | |
1780 | } | |
1781 | if (!$this->context) { | |
1782 | return null; | |
1783 | } | |
1784 | ||
1785 | if ($this->context->contextlevel == CONTEXT_MODULE) { | |
c13ac85d | 1786 | $modinfo = get_fast_modinfo($this->get_course()); |
1787 | $this->coursemodule = $modinfo->get_cm($this->context->instanceid); | |
bbd0e548 DW |
1788 | return $this->coursemodule; |
1789 | } | |
1790 | return null; | |
1791 | } | |
1792 | ||
1793 | /** | |
e5403f8c | 1794 | * Get context module. |
bbd0e548 DW |
1795 | * |
1796 | * @return context | |
1797 | */ | |
1798 | public function get_context() { | |
1799 | return $this->context; | |
1800 | } | |
1801 | ||
1802 | /** | |
e5403f8c DW |
1803 | * Get the current course. |
1804 | * | |
bbd0e548 DW |
1805 | * @return mixed stdClass|null The course |
1806 | */ | |
1807 | public function get_course() { | |
1808 | global $DB; | |
e5403f8c | 1809 | |
db381d5c | 1810 | if ($this->course && is_object($this->course)) { |
bbd0e548 DW |
1811 | return $this->course; |
1812 | } | |
1813 | ||
1814 | if (!$this->context) { | |
1815 | return null; | |
1816 | } | |
e5403f8c DW |
1817 | $params = array('id' => $this->get_course_context()->instanceid); |
1818 | $this->course = $DB->get_record('course', $params, '*', MUST_EXIST); | |
1819 | ||
bbd0e548 DW |
1820 | return $this->course; |
1821 | } | |
1822 | ||
7faf78cb HB |
1823 | /** |
1824 | * Count the number of intro attachments. | |
1825 | * | |
1826 | * @return int | |
1827 | */ | |
1828 | protected function count_attachments() { | |
1829 | ||
1830 | $fs = get_file_storage(); | |
1831 | $files = $fs->get_area_files($this->get_context()->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, | |
1832 | 0, 'id', false); | |
1833 | ||
1834 | return count($files); | |
1835 | } | |
1836 | ||
1837 | /** | |
1838 | * Are there any intro attachments to display? | |
1839 | * | |
1840 | * @return boolean | |
1841 | */ | |
1842 | protected function has_visible_attachments() { | |
1843 | return ($this->count_attachments() > 0); | |
1844 | } | |
1845 | ||
bbd0e548 | 1846 | /** |
e5403f8c | 1847 | * Return a grade in user-friendly form, whether it's a scale or not. |
bbd0e548 | 1848 | * |
9682626e | 1849 | * @param mixed $grade int|null |
bf78ebd6 | 1850 | * @param boolean $editing Are we allowing changes to this grade? |
2a4fbc32 SH |
1851 | * @param int $userid The user id the grade belongs to |
1852 | * @param int $modified Timestamp from when the grade was last modified | |
bbd0e548 DW |
1853 | * @return string User-friendly representation of grade |
1854 | */ | |
bf78ebd6 | 1855 | public function display_grade($grade, $editing, $userid=0, $modified=0) { |
bbd0e548 DW |
1856 | global $DB; |
1857 | ||
1858 | static $scalegrades = array(); | |
1859 | ||
be79d93f DW |
1860 | $o = ''; |
1861 | ||
2a4fbc32 | 1862 | if ($this->get_instance()->grade >= 0) { |
e5403f8c | 1863 | // Normal number. |
e7ade405 | 1864 | if ($editing && $this->get_instance()->grade > 0) { |
2d8a9ce9 DW |
1865 | if ($grade < 0) { |
1866 | $displaygrade = ''; | |
1867 | } else { | |
40e0baf0 | 1868 | $displaygrade = format_float($grade, $this->get_grade_item()->get_decimals()); |
2d8a9ce9 | 1869 | } |
e5403f8c DW |
1870 | $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' . |
1871 | get_string('usergrade', 'assign') . | |
1872 | '</label>'; | |
1873 | $o .= '<input type="text" | |
1874 | id="quickgrade_' . $userid . '" | |
1875 | name="quickgrade_' . $userid . '" | |
1876 | value="' . $displaygrade . '" | |
1877 | size="6" | |
1878 | maxlength="10" | |
1879 | class="quickgrade"/>'; | |
40e0baf0 | 1880 | $o .= ' / ' . format_float($this->get_instance()->grade, $this->get_grade_item()->get_decimals()); |
bf78ebd6 | 1881 | return $o; |
bbd0e548 | 1882 | } else { |
a1e54f4d | 1883 | if ($grade == -1 || $grade === null) { |
be79d93f | 1884 | $o .= '-'; |
a1e54f4d | 1885 | } else { |
0d64c75f PN |
1886 | $item = $this->get_grade_item(); |
1887 | $o .= grade_format_gradevalue($grade, $item); | |
1888 | if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) { | |
1889 | // If displaying the raw grade, also display the total value. | |
40e0baf0 | 1890 | $o .= ' / ' . format_float($this->get_instance()->grade, $item->get_decimals()); |
0d64c75f | 1891 | } |
a1e54f4d | 1892 | } |
0d64c75f | 1893 | return $o; |
bbd0e548 DW |
1894 | } |
1895 | ||
2a4fbc32 | 1896 | } else { |
e5403f8c | 1897 | // Scale. |
bbd0e548 DW |
1898 | if (empty($this->cache['scale'])) { |
1899 | if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) { | |
1900 | $this->cache['scale'] = make_menu_from_list($scale->scale); | |
1901 | } else { | |
be79d93f DW |
1902 | $o .= '-'; |
1903 | return $o; | |
bbd0e548 DW |
1904 | } |
1905 | } | |
bf78ebd6 | 1906 | if ($editing) { |
e5403f8c DW |
1907 | $o .= '<label class="accesshide" |
1908 | for="quickgrade_' . $userid . '">' . | |
1909 | get_string('usergrade', 'assign') . | |
1910 | '</label>'; | |
7400be1b | 1911 | $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">'; |
bf78ebd6 DW |
1912 | $o .= '<option value="-1">' . get_string('nograde') . '</option>'; |
1913 | foreach ($this->cache['scale'] as $optionid => $option) { | |
1914 | $selected = ''; | |
1915 | if ($grade == $optionid) { | |
1916 | $selected = 'selected="selected"'; | |
1917 | } | |
1918 | $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>'; | |
1919 | } | |
1920 | $o .= '</select>'; | |
bf78ebd6 DW |
1921 | return $o; |
1922 | } else { | |
1923 | $scaleid = (int)$grade; | |
1924 | if (isset($this->cache['scale'][$scaleid])) { | |
be79d93f DW |
1925 | $o .= $this->cache['scale'][$scaleid]; |
1926 | return $o; | |
bf78ebd6 | 1927 | } |
be79d93f DW |
1928 | $o .= '-'; |
1929 | return $o; | |
bbd0e548 | 1930 | } |
bbd0e548 DW |
1931 | } |
1932 | } | |
1933 | ||
bb690849 | 1934 | /** |
1b2f9dc6 RW |
1935 | * Get the submission status/grading status for all submissions in this assignment for the |
1936 | * given paticipants. | |
1937 | * | |
eb43ef6c | 1938 | * These statuses match the available filters (requiregrading, submitted, notsubmitted, grantedextension). |
bb690849 DW |
1939 | * If this is a group assignment, group info is also returned. |
1940 | * | |
1b2f9dc6 RW |
1941 | * @param array $participants an associative array where the key is the participant id and |
1942 | * the value is the participant record. | |
1943 | * @return array an associative array where the key is the participant id and the value is | |
1944 | * the participant record. | |
bb690849 | 1945 | */ |
1b2f9dc6 | 1946 | private function get_submission_info_for_participants($participants) { |
bb690849 DW |
1947 | global $DB; |
1948 | ||
bb690849 DW |
1949 | if (empty($participants)) { |
1950 | return $participants; | |
1951 | } | |
1952 | ||
1953 | list($insql, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED); | |
1954 | ||
1955 | $assignid = $this->get_instance()->id; | |
1956 | $params['assignmentid1'] = $assignid; | |
1957 | $params['assignmentid2'] = $assignid; | |
eb43ef6c | 1958 | $params['assignmentid3'] = $assignid; |
bb690849 | 1959 | |
eb43ef6c | 1960 | $fields = 'SELECT u.id, s.status, s.timemodified AS stime, g.timemodified AS gtime, g.grade, uf.extensionduedate'; |
c06ce32d | 1961 | $from = ' FROM {user} u |
bb690849 DW |
1962 | LEFT JOIN {assign_submission} s |
1963 | ON u.id = s.userid | |
1964 | AND s.assignment = :assignmentid1 | |
1965 | AND s.latest = 1 | |
1966 | LEFT JOIN {assign_grades} g | |
1967 | ON u.id = g.userid | |
1968 | AND g.assignment = :assignmentid2 | |
1969 | AND g.attemptnumber = s.attemptnumber | |
eb43ef6c TB |
1970 | LEFT JOIN {assign_user_flags} uf |
1971 | ON u.id = uf.userid | |
1972 | AND uf.assignment = :assignmentid3 | |
c06ce32d AN |
1973 | '; |
1974 | $where = ' WHERE u.id ' . $insql; | |
1975 | ||
1976 | if (!empty($this->get_instance()->blindmarking)) { | |
1977 | $from .= 'LEFT JOIN {assign_user_mapping} um | |
1978 | ON u.id = um.userid | |
eb43ef6c TB |
1979 | AND um.assignment = :assignmentid4 '; |
1980 | $params['assignmentid4'] = $assignid; | |
c06ce32d AN |
1981 | $fields .= ', um.id as recordid '; |
1982 | } | |
1983 | ||
1984 | $sql = "$fields $from $where"; | |
bb690849 DW |
1985 | |
1986 | $records = $DB->get_records_sql($sql, $params); | |
1987 | ||
1988 | if ($this->get_instance()->teamsubmission) { | |
1989 | // Get all groups. | |
1990 | $allgroups = groups_get_all_groups($this->get_course()->id, | |
1991 | array_keys($participants), | |
1992 | $this->get_instance()->teamsubmissiongroupingid, | |
1993 | 'DISTINCT g.id, g.name'); | |
1994 | ||
1995 | } | |
1996 | foreach ($participants as $userid => $participant) { | |
1997 | $participants[$userid]->fullname = $this->fullname($participant); | |
1998 | $participants[$userid]->submitted = false; | |
1999 | $participants[$userid]->requiregrading = false; | |
eb43ef6c | 2000 | $participants[$userid]->grantedextension = false; |
bb690849 DW |
2001 | } |
2002 | ||
2003 | foreach ($records as $userid => $submissioninfo) { | |
2004 | // These filters are 100% the same as the ones in the grading table SQL. | |
2005 | $submitted = false; | |
2006 | $requiregrading = false; | |
eb43ef6c | 2007 | $grantedextension = false; |
bb690849 DW |
2008 | |
2009 | if (!empty($submissioninfo->stime) && $submissioninfo->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) { | |
2010 | $submitted = true; | |
2011 | } | |
2012 | ||
2013 | if ($submitted && ($submissioninfo->stime >= $submissioninfo->gtime || | |
2014 | empty($submissioninfo->gtime) || | |
2015 | $submissioninfo->grade === null)) { | |
2016 | $requiregrading = true; | |
2017 | } | |
2018 | ||
eb43ef6c TB |
2019 | if (!empty($submissioninfo->extensionduedate)) { |
2020 | $grantedextension = true; | |
2021 | } | |
2022 | ||
bb690849 DW |
2023 | $participants[$userid]->submitted = $submitted; |
2024 | $participants[$userid]->requiregrading = $requiregrading; | |
eb43ef6c | 2025 | $participants[$userid]->grantedextension = $grantedextension; |
bb690849 DW |
2026 | if ($this->get_instance()->teamsubmission) { |
2027 | $group = $this->get_submission_group($userid); | |
2028 | if ($group) { | |
2029 | $participants[$userid]->groupid = $group->id; | |
2030 | $participants[$userid]->groupname = $group->name; | |
2031 | } | |
2032 | } | |
2033 | } | |
2034 | return $participants; | |
2035 | } | |
2036 | ||
1b2f9dc6 RW |
2037 | /** |
2038 | * Get the submission status/grading status for all submissions in this assignment. | |
eb43ef6c | 2039 | * These statuses match the available filters (requiregrading, submitted, notsubmitted, grantedextension). |
1b2f9dc6 RW |
2040 | * If this is a group assignment, group info is also returned. |
2041 | * | |
2042 | * @param int $currentgroup | |
064f1503 | 2043 | * @param boolean $tablesort Apply current user table sorting preferences. |
eb43ef6c TB |
2044 | * @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'grantedextension', |
2045 | * 'groupid', 'groupname' | |
1b2f9dc6 | 2046 | */ |
064f1503 DW |
2047 | public function list_participants_with_filter_status_and_group($currentgroup, $tablesort = false) { |
2048 | $participants = $this->list_participants($currentgroup, false, $tablesort); | |
1b2f9dc6 RW |
2049 | |
2050 | if (empty($participants)) { | |
2051 | return $participants; | |
2052 | } else { | |
2053 | return $this->get_submission_info_for_participants($participants); | |
2054 | } | |
2055 | } | |
2056 | ||
064f1503 DW |
2057 | /** |
2058 | * Return a valid order by segment for list_participants that matches | |
2059 | * the sorting of the current grading table. Not every field is supported, | |
2060 | * we are only concerned with a list of users so we can't search on anything | |
2061 | * that is not part of the user information (like grading statud or last modified stuff). | |
2062 | * | |
2063 | * @return string Order by clause for list_participants | |
2064 | */ | |
2065 | private function get_grading_sort_sql() { | |
2066 | $usersort = flexible_table::get_sort_for_table('mod_assign_grading'); | |
2067 | $extrauserfields = get_extra_user_fields($this->get_context()); | |
2068 | ||
2069 | $userfields = explode(',', user_picture::fields('', $extrauserfields)); | |
2070 | $orderfields = explode(',', $usersort); | |
2071 | $validlist = []; | |
2072 | ||
2073 | foreach ($orderfields as $orderfield) { | |
2074 | $orderfield = trim($orderfield); | |
2075 | foreach ($userfields as $field) { | |
2076 | $parts = explode(' ', $orderfield); | |
2077 | if ($parts[0] == $field) { | |
2078 | // Prepend the user table prefix and count this as a valid order field. | |
2079 | array_push($validlist, 'u.' . $orderfield); | |
2080 | } | |
2081 | } | |
2082 | } | |
2083 | // Produce a final list. | |
2084 | $result = implode(',', $validlist); | |
2085 | if (empty($result)) { | |
2086 | // Fall back ordering when none has been set. | |
2087 | $result = 'u.lastname, u.firstname, u.id'; | |
2088 | } | |
2089 | ||
2090 | return $result; | |
2091 | } | |
2092 | ||
bbd0e548 | 2093 | /** |
e5403f8c DW |
2094 | * Load a list of users enrolled in the current course with the specified permission and group. |
2095 | * 0 for no group. | |
064f1503 | 2096 | * Apply any current sort filters from the grading table. |
bbd0e548 DW |
2097 | * |
2098 | * @param int $currentgroup | |
2099 | * @param bool $idsonly | |
2100 | * @return array List of user records | |
2101 | */ | |
064f1503 | 2102 | public function list_participants($currentgroup, $idsonly, $tablesort = false) { |
230ae095 | 2103 | global $DB, $USER; |
bc006a66 | 2104 | |
064f1503 DW |
2105 | // Get the last known sort order for the grading table. |
2106 | ||
bc006a66 DM |
2107 | if (empty($currentgroup)) { |
2108 | $currentgroup = 0; | |
2109 | } | |
2110 | ||
c46db93c DW |
2111 | $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users(); |
2112 | if (!isset($this->participants[$key])) { | |
eb4c4661 | 2113 | list($esql, $params) = get_enrolled_sql($this->context, 'mod/assign:submit', $currentgroup, |
1ecb8044 | 2114 | $this->show_only_active_users()); |
c46db93c | 2115 | |
eb4c4661 AN |
2116 | $fields = 'u.*'; |
2117 | $orderby = 'u.lastname, u.firstname, u.id'; | |
064f1503 | 2118 | |
eb4c4661 | 2119 | $additionaljoins = ''; |
230ae095 | 2120 | $additionalfilters = ''; |
eb4c4661 AN |
2121 | $instance = $this->get_instance(); |
2122 | if (!empty($instance->blindmarking)) { | |
2123 | $additionaljoins .= " LEFT JOIN {assign_user_mapping} um | |
2124 | ON u.id = um.userid | |
2125 | AND um.assignment = :assignmentid1 | |
2126 | LEFT JOIN {assign_submission} s | |
2127 | ON u.id = s.userid | |
2128 | AND s.assignment = :assignmentid2 | |
2129 | AND s.latest = 1 | |
2130 | "; | |
2131 | $params['assignmentid1'] = (int) $instance->id; | |
2132 | $params['assignmentid2'] = (int) $instance->id; | |
2133 | $fields .= ', um.id as recordid '; | |
2134 | ||
2135 | // Sort by submission time first, then by um.id to sort reliably by the blind marking id. | |
2136 | // Note, different DBs have different ordering of NULL values. | |
2137 | // Therefore we coalesce the current time into the timecreated field, and the max possible integer into | |
2138 | // the ID field. | |
064f1503 DW |
2139 | if (empty($tablesort)) { |
2140 | $orderby = "COALESCE(s.timecreated, " . time() . ") ASC, COALESCE(s.id, " . PHP_INT_MAX . ") ASC, um.id ASC"; | |
2141 | } | |
eb4c4661 AN |
2142 | } |
2143 | ||
230ae095 DW |
2144 | if ($instance->markingworkflow && |
2145 | $instance->markingallocation && | |
bfca67f0 PS |
2146 | !has_capability('mod/assign:manageallocations', $this->get_context()) && |
2147 | has_capability('mod/assign:grade', $this->get_context())) { | |
230ae095 DW |
2148 | |
2149 | $additionaljoins .= ' LEFT JOIN {assign_user_flags} uf | |
2150 | ON u.id = uf.userid | |
2151 | AND uf.assignment = :assignmentid3'; | |
2152 | ||
2153 | $params['assignmentid3'] = (int) $instance->id; | |
2154 | ||
2155 | $additionalfilters .= ' AND uf.allocatedmarker = :markerid'; | |
2156 | $params['markerid'] = $USER->id; | |
2157 | } | |
2158 | ||
eb4c4661 AN |
2159 | $sql = "SELECT $fields |
2160 | FROM {user} u | |
2161 | JOIN ($esql) je ON je.id = u.id | |
2162 | $additionaljoins | |
2163 | WHERE u.deleted = 0 | |
230ae095 | 2164 | $additionalfilters |
eb4c4661 AN |
2165 | ORDER BY $orderby"; |
2166 | ||
2167 | $users = $DB->get_records_sql($sql, $params); | |
2168 | ||
c46db93c | 2169 | $cm = $this->get_course_module(); |
c13ac85d | 2170 | $info = new \core_availability\info_module($cm); |
2171 | $users = $info->filter_user_list($users); | |
c46db93c DW |
2172 | |
2173 | $this->participants[$key] = $users; | |
bbd0e548 | 2174 | } |
a0e59f04 | 2175 | |
064f1503 DW |
2176 | if ($tablesort) { |
2177 | // Resort the user list according to the grading table sort and filter settings. | |
2178 | $sortedfiltereduserids = $this->get_grading_userid_list(true, ''); | |
2179 | $sortedfilteredusers = []; | |
2180 | foreach ($sortedfiltereduserids as $nextid) { | |
2181 | $nextid = intval($nextid); | |
2182 | if (isset($this->participants[$key][$nextid])) { | |
2183 | $sortedfilteredusers[$nextid] = $this->participants[$key][$nextid]; | |
2184 | } | |
2185 | } | |
2186 | $this->participants[$key] = $sortedfilteredusers; | |
2187 | } | |
2188 | ||
c46db93c DW |
2189 | if ($idsonly) { |
2190 | $idslist = array(); | |
2191 | foreach ($this->participants[$key] as $id => $user) { | |
2192 | $idslist[$id] = new stdClass(); | |
2193 | $idslist[$id]->id = $id; | |
a0e59f04 | 2194 | } |
c46db93c | 2195 | return $idslist; |
a0e59f04 | 2196 | } |
c46db93c | 2197 | return $this->participants[$key]; |
bbd0e548 DW |
2198 | } |
2199 | ||
1b2f9dc6 RW |
2200 | /** |
2201 | * Load a user if they are enrolled in the current course. Populated with submission | |
2202 | * status for this assignment. | |
2203 | * | |
2204 | * @param int $userid | |
2205 | * @return null|stdClass user record | |
2206 | */ | |
2207 | public function get_participant($userid) { | |
7ec6d873 | 2208 | global $DB, $USER; |
1b2f9dc6 | 2209 | |
7ec6d873 RW |
2210 | if ($userid == $USER->id) { |
2211 | $participant = clone ($USER); | |
2212 | } else { | |
2213 | $participant = $DB->get_record('user', array('id' => $userid)); | |
2214 | } | |
1b2f9dc6 RW |
2215 | if (!$participant) { |
2216 | return null; | |
2217 | } | |
2218 | ||
2219 | if (!is_enrolled($this->context, $participant, 'mod/assign:submit', $this->show_only_active_users())) { | |
2220 | return null; | |
2221 | } | |
2222 | ||
2223 | $result = $this->get_submission_info_for_participants(array($participant->id => $participant)); | |
2224 | return $result[$participant->id]; | |
2225 | } | |
2226 | ||
12a1a0da | 2227 | /** |
e5403f8c | 2228 | * Load a count of valid teams for this assignment. |
12a1a0da | 2229 | * |
bc006a66 | 2230 | * @param int $activitygroup Activity active group |
12a1a0da DW |
2231 | * @return int number of valid teams |
2232 | */ | |
bc006a66 DM |
2233 | public function count_teams($activitygroup = 0) { |
2234 | ||
2235 | $count = 0; | |
2236 | ||
2237 | $participants = $this->list_participants($activitygroup, true); | |
2238 | ||
2239 | // If a team submission grouping id is provided all good as all returned groups | |
2240 | // are the submission teams, but if no team submission grouping was specified | |
2241 | // $groups will contain all participants groups. | |
2242 | if ($this->get_instance()->teamsubmissiongroupingid) { | |
12a1a0da | 2243 | |
bc006a66 DM |
2244 | // We restrict the users to the selected group ones. |
2245 | $groups = groups_get_all_groups($this->get_course()->id, | |
2246 | array_keys($participants), | |
2247 | $this->get_instance()->teamsubmissiongroupingid, | |
2248 | 'DISTINCT g.id, g.name'); | |
12a1a0da | 2249 | |
bc006a66 DM |
2250 | $count = count($groups); |
2251 | ||
2252 | // When a specific group is selected we don't count the default group users. | |
2253 | if ($activitygroup == 0) { | |
e528997a AH |
2254 | if (empty($this->get_instance()->preventsubmissionnotingroup)) { |
2255 | // See if there are any users in the default group. | |
2256 | $defaultusers = $this->get_submission_group_members(0, true); | |
2257 | if (count($defaultusers) > 0) { | |
2258 | $count += 1; | |
2259 | } | |
bc006a66 | 2260 | } |
426bb5ae SL |
2261 | } else if ($activitygroup != 0 && empty($groups)) { |
2262 | // Set count to 1 if $groups returns empty. | |
2263 | // It means the group is not part of $this->get_instance()->teamsubmissiongroupingid. | |
2264 | $count = 1; | |
bc006a66 DM |
2265 | } |
2266 | } else { | |
2267 | // It is faster to loop around participants if no grouping was specified. | |
2268 | $groups = array(); | |
2269 | foreach ($participants as $participant) { | |
2270 | if ($group = $this->get_submission_group($participant->id)) { | |
2271 | $groups[$group->id] = true; | |
e528997a | 2272 | } else if (empty($this->get_instance()->preventsubmissionnotingroup)) { |
bc006a66 DM |
2273 | $groups[0] = true; |
2274 | } | |
2275 | } | |
12a1a0da | 2276 | |
bc006a66 | 2277 | $count = count($groups); |
12a1a0da | 2278 | } |
bc006a66 | 2279 | |
12a1a0da DW |
2280 | return $count; |
2281 | } | |
2282 | ||
bbd0e548 | 2283 | /** |
c494e18c | 2284 | * Load a count of active users enrolled in the current course with the specified permission and group. |
e5403f8c | 2285 | * 0 for no group. |
bbd0e548 DW |
2286 | * |
2287 | * @param int $currentgroup | |
2288 | * @return int number of matching users | |
2289 | */ | |
2290 | public function count_participants($currentgroup) { | |
a0e59f04 | 2291 | return count($this->list_participants($currentgroup, true)); |
bbd0e548 DW |
2292 | } |
2293 | ||
f70079b9 | 2294 | /** |
c494e18c | 2295 | * Load a count of active users submissions in the current module that require grading |
f70079b9 | 2296 | * This means the submission modification time is more recent than the |
7a9fd6da | 2297 | * grading modification time and the status is SUBMITTED. |
f70079b9 | 2298 | * |
ddeabbc5 | 2299 | * @param mixed $currentgroup int|null the group for counting (if null the function will determine it) |
f70079b9 DW |
2300 | * @return int number of matching submissions |
2301 | */ | |
ddeabbc5 | 2302 | public function count_submissions_need_grading($currentgroup = null) { |
f70079b9 DW |
2303 | global $DB; |
2304 | ||
8f7e1c05 DW |
2305 | if ($this->get_instance()->teamsubmission) { |
2306 | // This does not make sense for group assignment because the submission is shared. | |
2307 | return 0; | |
2308 | } | |
2309 | ||
ddeabbc5 JL |
2310 | if ($currentgroup === null) { |
2311 | $currentgroup = groups_get_activity_group($this->get_course_module(), true); | |
2312 | } | |
c494e18c | 2313 | list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true); |
8f7e1c05 DW |
2314 | |
2315 | $params['assignid'] = $this->get_instance()->id; | |
2316 | $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; | |
32ad5c86 | 2317 | $sqlscalegrade = $this->get_instance()->grade < 0 ? ' OR g.grade = -1' : ''; |
8f7e1c05 DW |
2318 | |
2319 | $sql = 'SELECT COUNT(s.userid) | |
2320 | FROM {assign_submission} s | |
2321 | LEFT JOIN {assign_grades} g ON | |
2322 | s.assignment = g.assignment AND | |
df211804 | 2323 | s.userid = g.userid AND |
ad030ab4 | 2324 | g.attemptnumber = s.attemptnumber |
0c7b6910 | 2325 | JOIN(' . $esql . ') e ON e.id = s.userid |
8f7e1c05 | 2326 | WHERE |
ad030ab4 | 2327 | s.latest = 1 AND |
8f7e1c05 DW |
2328 | s.assignment = :assignid AND |
2329 | s.timemodified IS NOT NULL AND | |
2330 | s.status = :submitted AND | |
32ad5c86 PS |
2331 | (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL ' |
2332 | . $sqlscalegrade . ')'; | |
f70079b9 | 2333 | |
8f7e1c05 | 2334 | return $DB->count_records_sql($sql, $params); |
f70079b9 DW |
2335 | } |
2336 | ||
bbd0e548 | 2337 | /** |
e5403f8c | 2338 | * Load a count of grades. |
b473171a DW |
2339 | * |
2340 | * @return int number of grades | |
2341 | */ | |
2342 | public function count_grades() { | |
2343 | global $DB; | |
2344 | ||
2345 | if (!$this->has_instance()) { | |
2346 | return 0; | |
2347 | } | |
2348 | ||
8f7e1c05 | 2349 | $currentgroup = groups_get_activity_group($this->get_course_module(), true); |
c494e18c | 2350 | list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true); |
8f7e1c05 DW |
2351 | |
2352 | $params['assignid'] = $this->get_instance()->id; | |
2353 | ||
2354 | $sql = 'SELECT COUNT(g.userid) | |
2355 | FROM {assign_grades} g | |
0c7b6910 | 2356 | JOIN(' . $esql . ') e ON e.id = g.userid |
8f7e1c05 | 2357 | WHERE g.assignment = :assignid'; |
b473171a DW |
2358 | |
2359 | return $DB->count_records_sql($sql, $params); | |
2360 | } | |
2361 | ||
2362 | /** | |
e5403f8c | 2363 | * Load a count of submissions. |
b473171a | 2364 | * |
ab14ab74 | 2365 | * @param bool $includenew When true, also counts the submissions with status 'new'. |
b473171a DW |
2366 | * @return int number of submissions |
2367 | */ | |
ab14ab74 | 2368 | public function count_submissions($includenew = false) { |
b473171a DW |
2369 | global $DB; |
2370 | ||
2371 | if (!$this->has_instance()) { | |
2372 | return 0; | |
2373 | } | |
2374 | ||
8f7e1c05 | 2375 | $params = array(); |
ab14ab74 FM |
2376 | $sqlnew = ''; |
2377 | ||
2378 | if (!$includenew) { | |
2379 | $sqlnew = ' AND s.status <> :status '; | |
2380 | $params['status'] = ASSIGN_SUBMISSION_STATUS_NEW; | |
2381 | } | |
b473171a DW |
2382 | |
2383 | if ($this->get_instance()->teamsubmission) { | |
8f7e1c05 | 2384 | // We cannot join on the enrolment tables for group submissions (no userid). |
df211804 | 2385 | $sql = 'SELECT COUNT(DISTINCT s.groupid) |
8f7e1c05 DW |
2386 | FROM {assign_submission} s |
2387 | WHERE | |
2388 | s.assignment = :assignid AND | |
2389 | s.timemodified IS NOT NULL AND | |
ab14ab74 FM |
2390 | s.userid = :groupuserid' . |
2391 | $sqlnew; | |
8f7e1c05 DW |
2392 | |
2393 | $params['assignid'] = $this->get_instance()->id; | |
2394 | $params['groupuserid'] = 0; | |
2395 | } else { | |
2396 | $currentgroup = groups_get_activity_group($this->get_course_module(), true); | |
ab14ab74 | 2397 | list($esql, $enrolparams) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true); |
8f7e1c05 | 2398 | |
ab14ab74 | 2399 | $params = array_merge($params, $enrolparams); |
8f7e1c05 DW |
2400 | $params['assignid'] = $this->get_instance()->id; |
2401 | ||
df211804 | 2402 | $sql = 'SELECT COUNT(DISTINCT s.userid) |
8f7e1c05 | 2403 | FROM {assign_submission} s |
0c7b6910 | 2404 | JOIN(' . $esql . ') e ON e.id = s.userid |
8f7e1c05 DW |
2405 | WHERE |
2406 | s.assignment = :assignid AND | |
ab14ab74 FM |
2407 | s.timemodified IS NOT NULL ' . |
2408 | $sqlnew; | |
4c4c7b3f | 2409 | |
b473171a | 2410 | } |
8f7e1c05 | 2411 | |
b473171a DW |
2412 | return $DB->count_records_sql($sql, $params); |
2413 | } | |
2414 | ||
2415 | /** | |
e5403f8c | 2416 | * Load a count of submissions with a specified status. |
bbd0e548 DW |
2417 | * |
2418 | * @param string $status The submission status - should match one of the constants | |
ddeabbc5 | 2419 | * @param mixed $currentgroup int|null the group for counting (if null the function will determine it) |
bbd0e548 DW |
2420 | * @return int number of matching submissions |
2421 | */ | |
ddeabbc5 | 2422 | public function count_submissions_with_status($status, $currentgroup = null) { |
bbd0e548 | 2423 | global $DB; |
8f7e1c05 | 2424 | |
ddeabbc5 JL |
2425 | if ($currentgroup === null) { |
2426 | $currentgroup = groups_get_activity_group($this->get_course_module(), true); | |
2427 | } | |
c494e18c | 2428 | list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true); |
8f7e1c05 DW |
2429 | |
2430 | $params['assignid'] = $this->get_instance()->id; | |
df211804 | 2431 | $params['assignid2'] = $this->get_instance()->id; |
8f7e1c05 | 2432 | $params['submissionstatus'] = $status; |
12a1a0da DW |
2433 | |
2434 | if ($this->get_instance()->teamsubmission) { | |
bc006a66 DM |
2435 | |
2436 | $groupsstr = ''; | |
2437 | if ($currentgroup != 0) { | |
2438 | // If there is an active group we should only display the current group users groups. | |
2439 | $participants = $this->list_participants($currentgroup, true); | |
2440 | $groups = groups_get_all_groups($this->get_course()->id, | |
2441 | array_keys($participants), | |
2442 | $this->get_instance()->teamsubmissiongroupingid, | |
2443 | 'DISTINCT g.id, g.name'); | |
426bb5ae SL |
2444 | if (empty($groups)) { |
2445 | // If $groups is empty it means it is not part of $this->get_instance()->teamsubmissiongroupingid. | |
2446 | // All submissions from students that do not belong to any of teamsubmissiongroupingid groups | |
2447 | // count towards groupid = 0. Setting to true as only '0' key matters. | |
2448 | $groups = [true]; | |
2449 | } | |
bc006a66 DM |
2450 | list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); |
2451 | $groupsstr = 's.groupid ' . $groupssql . ' AND'; | |
2452 | $params = $params + $groupsparams; | |
2453 | } | |
8f7e1c05 DW |
2454 | $sql = 'SELECT COUNT(s.groupid) |
2455 | FROM {assign_submission} s | |
2456 | WHERE | |
ad030ab4 | 2457 | s.latest = 1 AND |
8f7e1c05 DW |
2458 | s.assignment = :assignid AND |
2459 | s.timemodified IS NOT NULL AND | |
bc006a66 DM |
2460 | s.userid = :groupuserid AND ' |
2461 | . $groupsstr . ' | |
8f7e1c05 DW |
2462 | s.status = :submissionstatus'; |
2463 | $params['groupuserid'] = 0; | |
2464 | } else { | |
2465 | $sql = 'SELECT COUNT(s.userid) | |
2466 | FROM {assign_submission} s | |
0c7b6910 | 2467 | JOIN(' . $esql . ') e ON e.id = s.userid |
8f7e1c05 | 2468 | WHERE |
ad030ab4 | 2469 | s.latest = 1 AND |
8f7e1c05 DW |
2470 | s.assignment = :assignid AND |
2471 | s.timemodified IS NOT NULL AND | |
2472 | s.status = :submissionstatus'; | |
4c4c7b3f | 2473 | |
12a1a0da | 2474 | } |
8f7e1c05 | 2475 | |
12a1a0da | 2476 | return $DB->count_records_sql($sql, $params); |
bbd0e548 DW |
2477 | } |
2478 | ||
2479 | /** | |
2480 | * Utility function to get the userid for every row in the grading table | |
e5403f8c | 2481 | * so the order can be frozen while we iterate it. |
bbd0e548 | 2482 | * |
064f1503 DW |
2483 | * @param boolean $cached If true, the cached list from the session could be returned. |
2484 | * @param string $useridlistid String value used for caching the participant list. | |
bbd0e548 DW |
2485 | * @return array An array of userids |
2486 | */ | |
064f1503 | 2487 | protected function get_grading_userid_list($cached = false, $useridlistid = '') { |
88d88bd9 EL |
2488 | global $SESSION; |
2489 | ||
064f1503 DW |
2490 | if ($cached) { |
2491 | if (empty($useridlistid)) { | |
2492 | $useridlistid = $this->get_useridlist_key_id(); | |
2493 | } | |
2494 | $useridlistkey = $this->get_useridlist_key($useridlistid); | |
2495 | if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) { | |
2496 | $SESSION->mod_assign_useridlist[$useridlistkey] = $this->get_grading_userid_list(false, ''); | |
2497 | } | |
2498 | return $SESSION->mod_assign_useridlist[$useridlistkey]; | |
2499 | } | |
bbd0e548 | 2500 | $filter = get_user_preferences('assign_filter', ''); |
bf78ebd6 | 2501 | $table = new assign_grading_table($this, 0, $filter, 0, false); |
bbd0e548 DW |
2502 | |
2503 | $useridlist = $table->get_column_data('userid'); | |
2504 | ||
2505 | return $useridlist; | |
2506 | } | |
2507 | ||
bbd0e548 | 2508 | /** |
e5403f8c | 2509 | * Generate zip file from array of given files. |
bbd0e548 | 2510 | * |
e5403f8c DW |
2511 | * @param array $filesforzipping - array of files to pass into archive_to_pathname. |
2512 | * This array is indexed by the final file name and each | |
2513 | * element in the array is an instance of a stored_file object. | |
2514 | * @return path of temp file - note this returned file does | |
2515 | * not have a .zip extension - it is a temp file. | |
bbd0e548 | 2516 | */ |
47f48152 | 2517 | protected function pack_files($filesforzipping) { |
e5403f8c DW |
2518 | global $CFG; |
2519 | // Create path for new zip file. | |
2520 | $tempzip = tempnam($CFG->tempdir . '/', 'assignment_'); | |
2521 | // Zip files. | |
2522 | $zipper = new zip_packer(); | |
2523 | if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) { | |
2524 | return $tempzip; | |
bbd0e548 | 2525 | } |
e5403f8c | 2526 | return false; |
bbd0e548 DW |
2527 | } |
2528 | ||
bbd0e548 | 2529 | /** |
3f7b501e SH |
2530 | * Finds all assignment notifications that have yet to be mailed out, and mails them. |
2531 | * | |
e5403f8c | 2532 | * Cron function to be run periodically according to the moodle cron. |
bbd0e548 DW |
2533 | * |
2534 | * @return bool | |
2535 | */ | |
e5403f8c | 2536 | public static function cron() { |
3f7b501e | 2537 | global $DB; |
75f87a57 | 2538 | |
e5403f8c | 2539 | // Only ever send a max of one days worth of updates. |
75f87a57 DW |
2540 | $yesterday = time() - (24 * 3600); |
2541 | $timenow = time(); | |
a3a1b61b | 2542 | $lastruntime = $DB->get_field('task_scheduled', 'lastruntime', array('component' => 'mod_assign')); |
75f87a57 | 2543 | |
70cfc878 JF |
2544 | // Collect all submissions that require mailing. |
2545 | // Submissions are included if all are true: | |
2546 | // - The assignment is visible in the gradebook. | |
2547 | // - No previous notification has been sent. | |
851b5c7f | 2548 | // - The grader was a real user, not an automated process. |
8d5b7fc6 MG |
2549 | // - The grade was updated in the past 24 hours. |
2550 | // - If marking workflow is enabled, the workflow state is at 'released'. | |
f14afd29 | 2551 | $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities, a.hidegrader, |
cf29b1e0 | 2552 | g.*, g.timemodified as lastmodified, cm.id as cmid, um.id as recordid |
3f7b501e SH |
2553 | FROM {assign} a |
2554 | JOIN {assign_grades} g ON g.assignment = a.id | |
fdd3ebc5 | 2555 | LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid |
86dced43 TB |
2556 | JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id |
2557 | JOIN {modules} md ON md.id = cm.module AND md.name = 'assign' | |
fdd3ebc5 | 2558 | JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name |
cf29b1e0 | 2559 | LEFT JOIN {assign_user_mapping} um ON g.id = um.userid AND um.assignment = a.id |
8d5b7fc6 MG |
2560 | WHERE (a.markingworkflow = 0 OR (a.markingworkflow = 1 AND uf.workflowstate = :wfreleased)) AND |
2561 | g.grader > 0 AND uf.mailed = 0 AND gri.hidden = 0 AND | |
2562 | g.timemodified >= :yesterday AND g.timemodified <= :today | |
f33be9eb | 2563 | ORDER BY a.course, cm.id"; |
e5403f8c | 2564 | |
70cfc878 JF |
2565 | $params = array( |
2566 | 'yesterday' => $yesterday, | |
2567 | 'today' => $timenow, | |
2568 | 'wfreleased' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED, | |
2569 | ); | |
3f7b501e | 2570 | $submissions = $DB->get_records_sql($sql, $params); |
75f87a57 | 2571 | |
ae7638f7 | 2572 | if (!empty($submissions)) { |
c8314005 | 2573 | |
ae7638f7 | 2574 | mtrace('Processing ' . count($submissions) . ' assignment submissions ...'); |
75f87a57 | 2575 | |
ae7638f7 DW |
2576 | // Preload courses we are going to need those. |
2577 | $courseids = array(); | |
2578 | foreach ($submissions as $submission) { | |
2579 | $courseids[] = $submission->course; | |
2580 | } | |
e5403f8c | 2581 | |
ae7638f7 DW |
2582 | // Filter out duplicates. |
2583 | $courseids = array_unique($courseids); | |
2584 | $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); | |
2585 | list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); | |
2586 | $sql = 'SELECT c.*, ' . $ctxselect . | |
2587 | ' FROM {course} c | |
2588 | LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel | |
2589 | WHERE c.id ' . $courseidsql; | |
e5403f8c | 2590 | |
ae7638f7 DW |
2591 | $params['contextlevel'] = CONTEXT_COURSE; |
2592 | $courses = $DB->get_records_sql($sql, $params); | |
e5403f8c | 2593 | |
ae7638f7 DW |
2594 | // Clean up... this could go on for a while. |
2595 | unset($courseids); | |
2596 | unset($ctxselect); | |
2597 | unset($courseidsql); | |
2598 | unset($params); | |
3f7b501e | 2599 | |
ae7638f7 DW |
2600 | // Message students about new feedback. |
2601 | foreach ($submissions as $submission) { | |
75f87a57 | 2602 | |
ae7638f7 | 2603 | mtrace("Processing assignment submission $submission->id ..."); |
75f87a57 | 2604 | |
ae7638f7 DW |
2605 | // Do not cache user lookups - could be too many. |
2606 | if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) { | |
2607 | mtrace('Could not find user ' . $submission->userid); | |
2608 | continue; | |
2609 | } | |
75f87a57 | 2610 | |
ae7638f7 DW |
2611 | // Use a cache to prevent the same DB queries happening over and over. |
2612 | if (!array_key_exists($submission->course, $courses)) { | |
2613 | mtrace('Could not find course ' . $submission->course); | |
2614 | continue; | |
2615 | } | |
2616 | $course = $courses[$submission->course]; | |
2617 | if (isset($course->ctxid)) { | |
2618 | // Context has not yet been preloaded. Do so now. | |
2619 | context_helper::preload_from_record($course); | |
2620 | } | |
75f87a57 | 2621 | |
ae7638f7 DW |
2622 | // Override the language and timezone of the "current" user, so that |
2623 | // mail is customised for the receiver. | |
2624 | cron_setup_user($user, $course); | |
2625 | ||
2626 | // Context lookups are already cached. | |
2627 | $coursecontext = context_course::instance($course->id); | |
2628 | if (!is_enrolled($coursecontext, $user->id)) { | |
2629 | $courseshortname = format_string($course->shortname, | |
2630 | true, | |
2631 | array('context' => $coursecontext)); | |
2632 | mtrace(fullname($user) . ' not an active participant in ' . $courseshortname); | |
2633 | continue; | |
2634 | } | |
75f87a57 | 2635 | |
ae7638f7 DW |
2636 | if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) { |
2637 | mtrace('Could not find grader ' . $submission->grader); | |
2638 | continue; | |
2639 | } | |
75f87a57 | 2640 | |
ccd6839a | 2641 | $modinfo = get_fast_modinfo($course, $user->id); |
86dced43 | 2642 | $cm = $modinfo->get_cm($submission->cmid); |
ae7638f7 | 2643 | // Context lookups are already cached. |
86dced43 | 2644 | $contextmodule = context_module::instance($cm->id); |
ae7638f7 | 2645 | |
ccd6839a TB |
2646 | if (!$cm->uservisible) { |
2647 | // Hold mail notification for assignments the user cannot access until later. | |
75f87a57 DW |
2648 | continue; |
2649 | } | |
bbd0e548 | 2650 | |
f14afd29 | 2651 | // Notify the student. Default to the non-anon version. |
ae7638f7 | 2652 | $messagetype = 'feedbackavailable'; |
f14afd29 AW |
2653 | // Message type needs 'anon' if "hidden grading" is enabled and the student |
2654 | // doesn't have permission to see the grader. | |
2655 | if ($submission->hidegrader && !has_capability('mod/assign:showhiddengrader', $contextmodule, $user)) { | |
2656 | $messagetype = 'feedbackavailableanon'; | |
2657 | // There's no point in having an "anonymous grader" if the notification email | |
91a2215e DM |
2658 | // comes from them. Send the email from the noreply user instead. |
2659 | $grader = core_user::get_noreply_user(); | |
f14afd29 AW |
2660 | } |
2661 | ||
ae7638f7 DW |
2662 | $eventtype = 'assign_notification'; |
2663 | $updatetime = $submission->lastmodified; | |
2664 | $modulename = get_string('modulename', 'assign'); | |
75f87a57 | 2665 | |
ae7638f7 DW |
2666 | $uniqueid = 0; |
2667 | if ($submission->blindmarking && !$submission->revealidentities) { | |
cf29b1e0 | 2668 | if (empty($submission->recordid)) { |
0163e5d2 | 2669 | $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $grader->id); |
cf29b1e0 AN |
2670 | } else { |
2671 | $uniqueid = $submission->recordid; | |
2672 | } | |
ae7638f7 DW |
2673 | } |
2674 | $showusers = $submission->blindmarking && !$submission->revealidentities; | |
2675 | self::send_assignment_notification($grader, | |
2676 | $user, | |
2677 | $messagetype, | |
2678 | $eventtype, | |
2679 | $updatetime, | |
86dced43 | 2680 | $cm, |
ae7638f7 DW |
2681 | $contextmodule, |
2682 | $course, | |
2683 | $modulename, | |
2684 | $submission->name, | |
2685 | $showusers, | |
2686 | $uniqueid); | |
2687 | ||
2688 | $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment)); | |
2689 | if ($flags) { | |
2690 | $flags->mailed = 1; | |
2691 | $DB->update_record('assign_user_flags', $flags); | |
2692 | } else { | |
2693 | $flags = new stdClass(); | |
2694 | $flags->userid = $user->id; | |
2695 | $flags->assignment = $submission->assignment; | |
2696 | $flags->mailed = 1; | |
2697 | $DB->insert_record('assign_user_flags', $flags); | |
2698 | } | |
b473171a | 2699 | |
ae7638f7 | 2700 | mtrace('Done'); |
df211804 | 2701 | } |
ae7638f7 | 2702 | mtrace('Done processing ' . count($submissions) . ' assignment submissions'); |
75f87a57 | 2703 | |
ae7638f7 DW |
2704 | cron_setup_user(); |
2705 | ||
2706 | // Free up memory just to be sure. | |
2707 | unset($courses); | |
75f87a57 | 2708 | } |
75f87a57 | 2709 | |
ae7638f7 DW |
2710 | // Update calendar events to provide a description. |
2711 | $sql = 'SELECT id | |
2712 | FROM {assign} | |
2713 | WHERE | |
a3a1b61b | 2714 | allowsubmissionsfromdate >= :lastruntime AND |
ae7638f7 DW |
2715 | allowsubmissionsfromdate <= :timenow AND |
2716 | alwaysshowdescription = 0'; | |
a3a1b61b | 2717 | $params = array('lastruntime' => $lastruntime, 'timenow' => $timenow); |
ae7638f7 DW |
2718 | $newlyavailable = $DB->get_records_sql($sql, $params); |
2719 | foreach ($newlyavailable as $record) { | |
2720 | $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST); | |
2721 | $context = context_module::instance($cm->id); | |
3f7b501e | 2722 | |
ae7638f7 DW |
2723 | $assignment = new assign($context, null, null); |
2724 | $assignment->update_calendar($cm->id); | |
2725 | } | |
bbd0e548 DW |
2726 | |
2727 | return true; | |
2728 | } | |
2729 | ||
d6c673ed DW |
2730 | /** |
2731 | * Mark in the database that this grade record should have an update notification sent by cron. | |
2732 | * | |
2733 | * @param stdClass $grade a grade record keyed on id | |
a4b10a52 | 2734 | * @param bool $mailedoverride when true, flag notification to be sent again. |
d6c673ed DW |
2735 | * @return bool true for success |
2736 | */ | |
a4b10a52 | 2737 | public function notify_grade_modified($grade, $mailedoverride = false) { |
d6c673ed DW |
2738 | global $DB; |
2739 | ||
df211804 | 2740 | $flags = $this->get_user_flags($grade->userid, true); |
a4b10a52 | 2741 | if ($flags->mailed != 1 || $mailedoverride) { |
df211804 | 2742 | $flags->mailed = 0; |
d6c673ed DW |
2743 | } |
2744 | ||
df211804 DW |
2745 | return $this->update_user_flags($flags); |
2746 | } | |
2747 | ||
2748 | /** | |
2749 | * Update user flags for this user in this assignment. | |
2750 | * | |
2751 | * @param stdClass $flags a flags record keyed on id | |
2752 | * @return bool true for success | |
2753 | */ | |
2754 | public function update_user_flags($flags) { | |
2755 | global $DB; | |
2756 | if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) { | |
2757 | return false; | |
2758 | } | |
2759 | ||
2760 | $result = $DB->update_record('assign_user_flags', $flags); | |
2761 | return $result; | |
d6c673ed DW |
2762 | } |
2763 | ||
bbd0e548 | 2764 | /** |
e5403f8c | 2765 | * Update a grade in the grade table for the assignment and in the gradebook. |
bbd0e548 DW |
2766 | * |
2767 | * @param stdClass $grade a grade record keyed on id | |
ab38cb28 | 2768 | * @param bool $reopenattempt If the attempt reopen method is manual, allow another attempt at this assignment. |
bbd0e548 DW |
2769 | * @return bool true for success |
2770 | */ | |
ab38cb28 | 2771 | public function update_grade($grade, $reopenattempt = false) { |
bbd0e548 DW |
2772 | global $DB; |
2773 | ||
2774 | $grade->timemodified = time(); | |
2775 | ||
f8d107b3 DM |
2776 | if (!empty($grade->workflowstate)) { |
2777 | $validstates = $this->get_marking_workflow_states_for_current_user(); | |
2778 | if (!array_key_exists($grade->workflowstate, $validstates)) { | |
2779 | return false; | |
2780 | } | |
2781 | } | |
2782 | ||
bbd0e548 DW |
2783 | if ($grade->grade && $grade->grade != -1) { |
2784 | if ($this->get_instance()->grade > 0) { | |
2785 | if (!is_numeric($grade->grade)) { | |
2786 | return false; | |
2787 | } else if ($grade->grade > $this->get_instance()->grade) { | |
2788 | return false; | |
2789 | } else if ($grade->grade < 0) { | |
2790 | return false; | |
2791 | } | |
2792 | } else { | |
e5403f8c | 2793 | // This is a scale. |
bbd0e548 DW |
2794 | if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) { |
2795 | $scaleoptions = make_menu_from_list($scale->scale); | |
2796 | if (!array_key_exists((int) $grade->grade, $scaleoptions)) { | |
2797 | return false; | |
2798 | } | |
2799 | } | |
2800 | } | |
2801 | } | |
2802 | ||
a8fdb36c DW |
2803 | if (empty($grade->attemptnumber)) { |
2804 | // Set it to the default. | |
2805 | $grade->attemptnumber = 0; | |
2806 | } | |
e77bacab | 2807 | $DB->update_record('assign_grades', $grade); |
df211804 | 2808 | |
df211804 DW |
2809 | $submission = null; |
2810 | if ($this->get_instance()->teamsubmission) { | |
75c21e7e AG |
2811 | if (isset($this->mostrecentteamsubmission)) { |
2812 | $submission = $this->mostrecentteamsubmission; | |
2813 | } else { | |
2814 | $submission = $this->get_group_submission($grade->userid, 0, false); | |
2815 | } | |
df211804 DW |
2816 | } else { |
2817 | $submission = $this->get_user_submission($grade->userid, false); | |
2818 | } | |
767fda9b | 2819 | |
75c21e7e | 2820 | // Only push to gradebook if the update is for the most recent attempt. |
df211804 DW |
2821 | if ($submission && $submission->attemptnumber != $grade->attemptnumber) { |
2822 | return true; | |
2823 | } | |
2824 | ||
bd3ee807 MN |
2825 | if ($this->gradebook_item_update(null, $grade)) { |
2826 | \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger(); | |
2827 | } | |
e77bacab MP |
2828 | |
2829 | // If the conditions are met, allow another attempt. | |
2830 | if ($submission) { | |
2831 | $this->reopen_submission_if_required($grade->userid, | |
2832 | $submission, | |
2833 | $reopenattempt); | |
bbd0e548 | 2834 | } |
bd3ee807 | 2835 | |
e77bacab | 2836 | return true; |
bbd0e548 DW |
2837 | } |
2838 | ||
9e795179 | 2839 | /** |
e5403f8c | 2840 | * View the grant extension date page. |
9e795179 DW |
2841 | * |
2842 | * Uses url parameters 'userid' | |
2843 | * or from parameter 'selectedusers' | |
e5403f8c | 2844 | * |
9e795179 DW |
2845 | * @param moodleform $mform - Used for validation of the submitted data |
2846 | * @return string | |
2847 | */ | |
47f48152 | 2848 | protected function view_grant_extension($mform) { |
ffcfffbe | 2849 | global $CFG; |
9e795179 DW |
2850 | require_once($CFG->dirroot . '/mod/assign/extensionform.php'); |
2851 | ||
2852 | $o = ''; | |
26670f5e | 2853 | |
9e795179 | 2854 | $data = new stdClass(); |
26670f5e | 2855 | $data->id = $this->get_course_module()->id; |
9e795179 | 2856 | |
26670f5e | 2857 | $formparams = array( |
ffcfffbe IT |
2858 | 'instance' => $this->get_instance(), |
2859 | 'assign' => $this | |
26670f5e JF |
2860 | ); |
2861 | ||
ffcfffbe IT |
2862 | $users = optional_param('userid', 0, PARAM_INT); |
2863 | if (!$users) { | |
2864 | $users = required_param('selectedusers', PARAM_SEQUENCE); | |
2865 | } | |
2866 | $userlist = explode(',', $users); | |
26670f5e | 2867 | |
d685b959 IT |
2868 | $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); |
2869 | $maxoverride = array('allowsubmissionsfromdate' => 0, 'duedate' => 0, 'cutoffdate' => 0); | |
2870 | foreach ($userlist as $userid) { | |
2871 | // To validate extension date with users overrides. | |
2872 | $override = $this->override_exists($userid); | |
2873 | foreach ($keys as $key) { | |
2874 | if ($override->{$key}) { | |
2875 | if ($maxoverride[$key] < $override->{$key}) { | |
2876 | $maxoverride[$key] = $override->{$key}; | |
2877 | } | |
2878 | } else if ($maxoverride[$key] < $this->get_instance()->{$key}) { | |
2879 | $maxoverride[$key] = $this->get_instance()->{$key}; | |
2880 | } | |
2881 | } | |
2882 | } | |
2883 | foreach ($keys as $key) { | |
2884 | if ($maxoverride[$key]) { | |
2885 | $this->get_instance()->{$key} = $maxoverride[$key]; | |
2886 | } | |
2887 | } | |
2888 | ||
ffcfffbe | 2889 | $formparams['userlist'] = $userlist; |
9e795179 | 2890 | |
ffcfffbe IT |
2891 | $data->selectedusers = $users; |
2892 | $data->userid = 0; | |
26670f5e | 2893 | |
ffcfffbe IT |
2894 | if (empty($mform)) { |
2895 | $mform = new mod_assign_extension_form(null, $formparams); | |
9e795179 | 2896 | } |
26670f5e | 2897 | $mform->set_data($data); |
e5403f8c DW |
2898 | $header = new assign_header($this->get_instance(), |
2899 | $this->get_context(), | |
2900 | $this->show_intro(), | |
2901 | $this->get_course_module()->id, | |
2902 | get_string('grantextension', 'assign')); | |
2903 | $o .= $this->get_renderer()->render($header); | |
49d83b9d | 2904 | $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform)); |
9e795179 DW |
2905 | $o .= $this->view_footer(); |
2906 | return $o; | |
2907 | } | |
2908 | ||
12a1a0da | 2909 | /** |
e5403f8c | 2910 | * Get a list of the users in the same group as this user. |
12a1a0da DW |
2911 | * |
2912 | * @param int $groupid The id of the group whose members we want or 0 for the default group | |
2913 | * @param bool $onlyids Whether to retrieve only the user id's | |
b2885704 | 2914 | * @param bool $excludesuspended Whether to exclude suspended users |
12a1a0da DW |
2915 | * @return array The users (possibly id's only) |
2916 | */ | |
b2885704 | 2917 | public function get_submission_group_members($groupid, $onlyids, $excludesuspended = false) { |
12a1a0da DW |
2918 | $members = array(); |
2919 | if ($groupid != 0) { | |
64ccb37d | 2920 | $allusers = $this->list_participants($groupid, $onlyids); |
12a1a0da DW |
2921 | foreach ($allusers as $user) { |
2922 | if ($this->get_submission_group($user->id)) { | |
2923 | $members[] = $user; | |
2924 | } | |
2925 | } | |
2926 | } else { | |
2927 | $allusers = $this->list_participants(null, $onlyids); | |
2928 | foreach ($allusers as $user) { | |
2929 | if ($this->get_submission_group($user->id) == null) { | |
2930 | $members[] = $user; | |
2931 | } | |
2932 | } | |
2933 | } | |
4c4c7b3f | 2934 | // Exclude suspended users, if user can't see them. |
b2885704 | 2935 | if ($excludesuspended || !has_capability('moodle/course:viewsuspendedusers', $this->context)) { |
4c4c7b3f | 2936 | foreach ($members as $key => $member) { |
1ecb8044 | 2937 | if (!$this->is_active_user($member->id)) { |
4c4c7b3f RT |
2938 | unset($members[$key]); |
2939 | } | |
2940 | } | |
2941 | } | |
bc006a66 | 2942 | |
12a1a0da DW |
2943 | return $members; |
2944 | } | |
2945 | ||
2946 | /** | |
e5403f8c | 2947 | * Get a list of the users in the same group as this user that have not submitted the assignment. |
12a1a0da DW |
2948 | * |
2949 | * @param int $groupid The id of the group whose members we want or 0 for the default group | |
2950 | * @param bool $onlyids Whether to retrieve only the user id's | |
2951 | * @return array The users (possibly id's only) | |
2952 | */ | |
2953 | public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) { | |
e5403f8c DW |
2954 | $instance = $this->get_instance(); |
2955 | if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) { | |
12a1a0da DW |
2956 | return array(); |
2957 | } | |
2958 | $members = $this->get_submission_group_members($groupid, $onlyids); | |
2959 | ||
2960 | foreach ($members as $id => $member) { | |
2961 | $submission = $this->get_user_submission($member->id, false); | |
df211804 | 2962 | if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) { |
12a1a0da | 2963 | unset($members[$id]); |
88cfe469 DW |
2964 | } else { |
2965 | if ($this->is_blind_marking()) { | |
e5403f8c DW |
2966 | $members[$id]->alias = get_string('hiddenuser', 'assign') . |
2967 | $this->get_uniqueid_for_user($id); | |
88cfe469 | 2968 | } |
12a1a0da DW |
2969 | } |
2970 | } | |
2971 | return $members; | |
2972 | } | |
2973 | ||
2974 | /** | |
e5403f8c | 2975 | * Load the group submission object for a particular user, optionally creating it if required. |
12a1a0da DW |
2976 | * |
2977 | * @param int $userid The id of the user whose submission we want | |
e5403f8c DW |
2978 | * @param int $groupid The id of the group for this user - may be 0 in which |
2979 | * case it is determined from the userid. | |
12a1a0da | 2980 | * @param bool $create If set to true a new submission object will be created in the database |
9e3eee67 | 2981 | * with the status set to "new". |
df211804 | 2982 | * @param int $attemptnumber - -1 means the latest attempt |
12a1a0da DW |
2983 | * @return stdClass The submission |
2984 | */ | |
df211804 | 2985 | public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) { |
12a1a0da DW |
2986 | global $DB; |
2987 | ||
2988 | if ($groupid == 0) { | |
2989 | $group = $this->get_submission_group($userid); | |
2990 | if ($group) { | |
2991 | $groupid = $group->id; | |
2992 | } | |
2993 | } | |
2994 | ||
12a1a0da DW |
2995 | // Now get the group submission. |
2996 | $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0); | |
df211804 DW |
2997 | if ($attemptnumber >= 0) { |
2998 | $params['attemptnumber'] = $attemptnumber; | |
2999 | } | |
3000 | ||
3001 | // Only return the row with the highest attemptnumber. | |
3002 | $submission = null; | |
3003 | $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1); | |
3004 | if ($submissions) { | |
3005 | $submission = reset($submissions); | |
3006 | } | |
12a1a0da DW |
3007 | |
3008 | if ($submission) { | |
3009 | return $submission; | |
3010 | } | |
3011 | if ($create) { | |
3012 | $submission = new stdClass(); | |
e5403f8c DW |
3013 | $submission->assignment = $this->get_instance()->id; |
3014 | $submission->userid = 0; | |
3015 | $submission->groupid = $groupid; | |
12a1a0da DW |
3016 | $submission->timecreated = time(); |
3017 | $submission->timemodified = $submission->timecreated; | |
df211804 DW |
3018 | if ($attemptnumber >= 0) { |
3019 | $submission->attemptnumber = $attemptnumber; | |
12a1a0da | 3020 | } else { |
df211804 | 3021 | $submission->attemptnumber = 0; |
12a1a0da | 3022 | } |
1523f9e0 DW |
3023 | // Work out if this is the latest submission. |
3024 | $submission->latest = 0; | |
3025 | $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0); | |
3026 | if ($attemptnumber == -1) { | |
3027 | // This is a new submission so it must be the latest. | |
3028 | $submission->latest = 1; | |
3029 | } else { | |
3030 | // We need to work this out. | |
3031 | $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1); | |
3032 | if ($result) { | |
3033 | $latestsubmission = reset($result); | |
3034 | } | |
3035 | if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) { | |
3036 | $submission->latest = 1; | |
3037 | } | |
3038 | } | |
3039 | if ($submission->latest) { | |
3040 | // This is the case when we need to set latest to 0 for all the other attempts. | |
3041 | $DB->set_field('assign_submission', 'latest', 0, $params); | |
3042 | } | |
9e3eee67 | 3043 | $submission->status = ASSIGN_SUBMISSION_STATUS_NEW; |
12a1a0da | 3044 | $sid = $DB->insert_record('assign_submission', $submission); |
4781ff2e | 3045 | return $DB->get_record('assign_submission', array('id' => $sid)); |
12a1a0da DW |
3046 | } |
3047 | return false; | |
3048 | } | |
3049 | ||
df47b77f | 3050 | /** |
64220210 DW |
3051 | * View a summary listing of all assignments in the current course. |
3052 | * | |
3053 | * @return string | |
3054 | */ | |
3055 | private function view_course_index() { | |
3056 | global $USER; | |
3057 | ||
3058 | $o = ''; | |
3059 | ||
3060 | $course = $this->get_course(); | |
3061 | $strplural = get_string('modulenameplural', 'assign'); | |
3062 | ||
3063 | if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) { | |
3064 | $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural)); | |
3065 | $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id))); | |
3066 | return $o; | |
3067 | } | |
3068 | ||
09af1e28 | 3069 | $strsectionname = ''; |
64220210 DW |
3070 | $usesections = course_format_uses_sections($course->format); |
3071 | $modinfo = get_fast_modinfo($course); | |
3072 | ||
3073 | if ($usesections) { | |
09af1e28 | 3074 | $strsectionname = get_string('sectionname', 'format_'.$course->format); |
64220210 DW |
3075 | $sections = $modinfo->get_section_info_all(); |
3076 | } | |
3077 | $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname); | |
3078 | ||
3079 | $timenow = time(); | |
3080 | ||
3081 | $currentsection = ''; | |
3082 | foreach ($modinfo->instances['assign'] as $cm) { | |
3083 | if (!$cm->uservisible) { | |
3084 | continue; | |
3085 | } | |
3086 | ||
e5403f8c | 3087 | $timedue = $cms[$cm->id]->duedate; |
64220210 DW |
3088 | |
3089 | $sectionname = ''; | |
3090 | if ($usesections && $cm->sectionnum) { | |
3091 | $sectionname = get_section_name($course, $sections[$cm->sectionnum]); | |
3092 | } | |
3093 | ||
3094 | $submitted = ''; | |
3095 | $context = context_module::instance($cm->id); | |
3096 | ||
3097 | $assignment = new assign($context, $cm, $course); | |
3098 | ||
d685b959 IT |
3099 | // Apply overrides. |
3100 | $assignment->update_effective_access($USER->id); | |
3101 | $timedue = $assignment->get_instance()->duedate; | |
3102 | ||
64220210 DW |
3103 | if (has_capability('mod/assign:grade', $context)) { |
3104 | $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED); | |
3105 | ||
3106 | } else if (has_capability('mod/assign:submit', $context)) { | |
3c4fd881 DW |
3107 | if ($assignment->get_instance()->teamsubmission) { |
3108 | $usersubmission = $assignment->get_group_submission($USER->id, 0, false); | |
3109 | } else { | |
3110 | $usersubmission = $assignment->get_user_submission($USER->id, false); | |
3111 | } | |
64220210 DW |
3112 | |
3113 | if (!empty($usersubmission->status)) { | |
3114 | $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign'); | |
3115 | } else { | |
3116 | $submitted = get_string('submissionstatus_', 'assign'); | |
3117 | } | |
3118 | } | |
539af602 DW |
3119 | $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id); |
3120 | if (isset($gradinginfo->items[0]->grades[$USER->id]) && | |
3121 | !$gradinginfo->items[0]->grades[$USER->id]->hidden ) { | |
3122 | $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade; | |
64220210 DW |
3123 | } else { |
3124 | $grade = '-'; | |
3125 | } | |
3126 | ||
92c9be19 | 3127 | $courseindexsummary->add_assign_info($cm->id, $cm->get_formatted_name(), $sectionname, $timedue, $submitted, $grade); |
64220210 DW |
3128 | |
3129 | } | |
3130 | ||
3131 | $o .= $this->get_renderer()->render($courseindexsummary); | |
3132 | $o .= $this->view_footer(); | |
3133 | ||
3134 | return $o; | |
3135 | } | |
3136 | ||
3137 | /** | |
3138 | * View a page rendered by a plugin. | |
df47b77f | 3139 | * |
e5403f8c | 3140 | * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'. |
df47b77f DW |
3141 | * |
3142 | * @return string | |
3143 | */ | |
47f48152 | 3144 | protected function view_plugin_page() { |
df47b77f DW |
3145 | global $USER; |
3146 | ||
3147 | $o = ''; | |
3148 | ||
3149 | $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA); | |
82a8d0d2 | 3150 | $plugintype = required_param('plugin', PARAM_PLUGIN); |
df47b77f DW |
3151 | $pluginaction = required_param('pluginaction', PARAM_ALPHA); |
3152 | ||
3153 | $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype); | |
3154 | if (!$plugin) { | |
3155 | print_error('invalidformdata', ''); | |
3156 | return; | |
3157 | } | |
3158 | ||
3159 | $o .= $plugin->view_page($pluginaction); | |
3160 | ||
3161 | return $o; | |
3162 | } | |
3163 | ||
3164 | ||
12a1a0da DW |
3165 | /** |
3166 | * This is used for team assignments to get the group for the specified user. | |
3167 | * If the user is a member of multiple or no groups this will return false | |
3168 | * | |
3169 | * @param int $userid The id of the user whose submission we want | |
3170 | * @return mixed The group or false | |
3171 | */ | |
3172 | public function get_submission_group($userid) { | |
bc006a66 DM |
3173 | |
3174 | if (isset($this->usersubmissiongroups[$userid])) { | |
3175 | return $this->usersubmissiongroups[$userid]; | |
3176 | } | |
3177 | ||
ee5d4eef | 3178 | $groups = $this->get_all_groups($userid); |
12a1a0da | 3179 | if (count($groups) != 1) { |
bc006a66 DM |
3180 | $return = false; |
3181 | } else { | |
3182 | $return = array_pop($groups); | |
12a1a0da | 3183 | } |
bc006a66 DM |
3184 | |
3185 | // Cache the user submission group. | |
3186 | $this->usersubmissiongroups[$userid] = $return; | |
3187 | ||
3188 | return $return; | |
12a1a0da DW |
3189 | } |
3190 | ||
a059ef2e DP |
3191 | /** |
3192 | * Gets all groups the user is a member of. | |
3193 | * | |
3194 | * @param int $userid Teh id of the user who's groups we are checking | |
3195 | * @return array The group objects | |
3196 | */ | |
ce449f63 | 3197 | public function get_all_groups($userid) { |
ee5d4eef AH |
3198 | if (isset($this->usergroups[$userid])) { |
3199 | return $this->usergroups[$userid]; | |
3200 | } | |
3201 | ||
3202 | $grouping = $this->get_instance()->teamsubmissiongroupingid; | |
3203 | $return = groups_get_all_groups($this->get_course()->id, $userid, $grouping); | |
3204 | ||
3205 | $this->usergroups[$userid] = $return; | |
3206 | ||
3207 | return $return; | |
3208 | } | |
3209 | ||
9e795179 | 3210 | |
bbd0e548 | 3211 | /** |
e5403f8c DW |
3212 | * Display the submission that is used by a plugin. |
3213 | * | |
3214 | * Uses url parameters 'sid', 'gid' and 'plugin'. | |
3215 | * | |
bbd0e548 DW |
3216 | * @param string $pluginsubtype |
3217 | * @return string | |
3218 | */ | |
47f48152 | 3219 | protected function view_plugin_content($pluginsubtype) { |
bbd0e548 DW |
3220 | $o = ''; |
3221 | ||
3222 | $submissionid = optional_param('sid', 0, PARAM_INT); | |
3223 | $gradeid = optional_param('gid', 0, PARAM_INT); | |
82a8d0d2 | 3224 | $plugintype = required_param('plugin', PARAM_PLUGIN); |
bbd0e548 DW |
3225 | $item = null; |
3226 | if ($pluginsubtype == 'assignsubmission') { | |
3227 | $plugin = $this->get_submission_plugin_by_type($plugintype); | |
3228 | if ($submissionid <= 0) { | |
3229 | throw new coding_exception('Submission id should not b |