From f8d107b3a8b2a97ed7ca353761d04d0909e7a950 Mon Sep 17 00:00:00 2001 From: Dan Marsden Date: Tue, 2 Apr 2013 11:59:53 +1300 Subject: [PATCH] MDL-38359 Marking Allocation and Workflow code based on Marking workflow code from Damyon Wiese --- .../backup/moodle2/backup_assign_stepslib.php | 9 +- .../moodle2/restore_assign_stepslib.php | 6 + mod/assign/batchsetallocatedmarkerform.php | 62 +++ .../batchsetmarkingworkflowstateform.php | 62 +++ mod/assign/db/access.php | 41 +- mod/assign/db/install.xml | 6 +- mod/assign/db/log.php | 2 + mod/assign/db/upgrade.php | 35 +- mod/assign/gradingbatchoperationsform.php | 6 + mod/assign/gradingoptionsform.php | 7 +- mod/assign/gradingtable.php | 177 +++++++- mod/assign/lang/en/assign.php | 38 +- mod/assign/locallib.php | 419 +++++++++++++++++- mod/assign/mod_form.php | 9 + mod/assign/module.js | 12 + mod/assign/renderer.php | 2 + mod/assign/tests/generator/lib.php | 4 +- mod/assign/tests/locallib_test.php | 104 +++++ mod/assign/upgradelib.php | 2 + mod/assign/version.php | 2 +- 20 files changed, 970 insertions(+), 35 deletions(-) create mode 100644 mod/assign/batchsetallocatedmarkerform.php create mode 100644 mod/assign/batchsetmarkingworkflowstateform.php diff --git a/mod/assign/backup/moodle2/backup_assign_stepslib.php b/mod/assign/backup/moodle2/backup_assign_stepslib.php index 8696fa8e10ad4..fdbe03f4e2c48 100644 --- a/mod/assign/backup/moodle2/backup_assign_stepslib.php +++ b/mod/assign/backup/moodle2/backup_assign_stepslib.php @@ -64,7 +64,10 @@ protected function define_structure() { 'blindmarking', 'revealidentities', 'attemptreopenmethod', - 'maxattempts')); + 'maxattempts', + 'markingworkflow', + 'markingallocation')); + $userflags = new backup_nested_element('userflags'); @@ -73,7 +76,9 @@ protected function define_structure() { 'assignment', 'mailed', 'locked', - 'extensionduedate')); + 'extensionduedate', + 'workflowstate', + 'allocatedmarker')); $submissions = new backup_nested_element('submissions'); diff --git a/mod/assign/backup/moodle2/restore_assign_stepslib.php b/mod/assign/backup/moodle2/restore_assign_stepslib.php index 28f3e37a69038..648dcad954274 100644 --- a/mod/assign/backup/moodle2/restore_assign_stepslib.php +++ b/mod/assign/backup/moodle2/restore_assign_stepslib.php @@ -87,6 +87,12 @@ protected function process_assign($data) { if (!isset($data->cutoffdate)) { $data->cutoffdate = 0; } + if (!isset($data->markingworkflow)) { + $data->markingworkflow = 0; + } + if (!isset($data->markingallocation)) { + $data->markingallocation = 0; + } if (!empty($data->preventlatesubmissions)) { $data->cutoffdate = $data->duedate; diff --git a/mod/assign/batchsetallocatedmarkerform.php b/mod/assign/batchsetallocatedmarkerform.php new file mode 100644 index 0000000000000..9695d6929f20b --- /dev/null +++ b/mod/assign/batchsetallocatedmarkerform.php @@ -0,0 +1,62 @@ +. + +/** + * This file contains the forms to set the allocated marker for selected submissions. + * + * @package mod_assign + * @copyright 2013 Catalyst IT {@link http://www.catalyst.net.nz} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); + +require_once($CFG->libdir.'/formslib.php'); +require_once($CFG->dirroot . '/mod/assign/feedback/file/locallib.php'); + +/** + * Set allocated marker form. + * + * @package mod_assign + * @copyright 2013 Catalyst IT {@link http://www.catalyst.net.nz} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_assign_batch_set_allocatedmarker_form extends moodleform { + /** + * Define this form - called by the parent constructor + */ + public function definition() { + $mform = $this->_form; + $params = $this->_customdata; + + $mform->addElement('header', '', get_string('batchsetallocatedmarker', 'assign', count($params['users']))); + $mform->addElement('static', 'userslist', get_string('selectedusers', 'assign'), $params['usershtml']); + + $options = $params['markers']; + $mform->addElement('select', 'allocatedmarker', get_string('allocatedmarker', 'assign'), $options); + + $mform->addElement('hidden', 'id', $params['cm']); + $mform->setType('id', PARAM_INT); + $mform->addElement('hidden', 'action', 'setbatchmarkingallocation'); + $mform->setType('action', PARAM_ALPHA); + $mform->addElement('hidden', 'selectedusers', implode(',', $params['users'])); + $mform->setType('selectedusers', PARAM_SEQUENCE); + $this->add_action_buttons(true, get_string('savechanges')); + + } + +} + diff --git a/mod/assign/batchsetmarkingworkflowstateform.php b/mod/assign/batchsetmarkingworkflowstateform.php new file mode 100644 index 0000000000000..643e3666d4773 --- /dev/null +++ b/mod/assign/batchsetmarkingworkflowstateform.php @@ -0,0 +1,62 @@ +. + +/** + * This file contains the forms to set the marking workflow for selected submissions. + * + * @package mod_assign + * @copyright 2013 Catalyst IT {@link http://www.catalyst.net.nz} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); + +require_once($CFG->libdir.'/formslib.php'); +require_once($CFG->dirroot . '/mod/assign/feedback/file/locallib.php'); + +/** + * Set marking workflow form. + * + * @package mod_assign + * @copyright 2013 Catalyst IT {@link http://www.catalyst.net.nz} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_assign_batch_set_marking_workflow_state_form extends moodleform { + /** + * Define this form - called by the parent constructor + */ + public function definition() { + $mform = $this->_form; + $params = $this->_customdata; + + $mform->addElement('header', '', get_string('batchsetmarkingworkflowstateforusers', 'assign', count($params['users']))); + $mform->addElement('static', 'userslist', get_string('selectedusers', 'assign'), $params['usershtml']); + + $options = $params['markingworkflowstates']; + $mform->addElement('select', 'markingworkflowstate', get_string('markingworkflowstate', 'assign'), $options); + + $mform->addElement('hidden', 'id', $params['cm']); + $mform->setType('id', PARAM_INT); + $mform->addElement('hidden', 'action', 'setbatchmarkingworkflowstate'); + $mform->setType('action', PARAM_ALPHA); + $mform->addElement('hidden', 'selectedusers', implode(',', $params['users'])); + $mform->setType('selectedusers', PARAM_SEQUENCE); + $this->add_action_buttons(true, get_string('savechanges')); + + } + +} + diff --git a/mod/assign/db/access.php b/mod/assign/db/access.php index 7028038907ea4..a062248b3308e 100644 --- a/mod/assign/db/access.php +++ b/mod/assign/db/access.php @@ -103,6 +103,45 @@ ), -); + 'mod/assign:reviewgrades' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/grade:manage' + ), + 'mod/assign:releasegrades' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/grade:manage' + ), + + 'mod/assign:managegrades' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/grade:manage' + ), + + 'mod/assign:manageallocations' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/grade:manage' + ), + +); diff --git a/mod/assign/db/install.xml b/mod/assign/db/install.xml index 271062eeaac02..4cd7e49ef2498 100644 --- a/mod/assign/db/install.xml +++ b/mod/assign/db/install.xml @@ -1,5 +1,5 @@ - @@ -30,6 +30,8 @@ + + @@ -120,6 +122,8 @@ + + diff --git a/mod/assign/db/log.php b/mod/assign/db/log.php index 45f1958007902..88486b0974986 100644 --- a/mod/assign/db/log.php +++ b/mod/assign/db/log.php @@ -32,6 +32,7 @@ array('module'=>'assign', 'action'=>'lock submission', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'reveal identities', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'revert submission to draft', 'mtable'=>'assign', 'field'=>'name'), + array('module'=>'assign', 'action'=>'set marking workflow state', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'submission statement accepted', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'submit', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'submit for grading', 'mtable'=>'assign', 'field'=>'name'), @@ -46,4 +47,5 @@ array('module'=>'assign', 'action'=>'view submission grading table', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'view submit assignment form', 'mtable'=>'assign', 'field'=>'name'), array('module'=>'assign', 'action'=>'view feedback', 'mtable'=>'assign', 'field'=>'name'), + array('module'=>'assign', 'action'=>'view batch set marking workflow state', 'mtable'=>'assign', 'field'=>'name'), ); diff --git a/mod/assign/db/upgrade.php b/mod/assign/db/upgrade.php index d7136decfe065..5990ad15aeb5c 100644 --- a/mod/assign/db/upgrade.php +++ b/mod/assign/db/upgrade.php @@ -424,8 +424,39 @@ function xmldb_assign_upgrade($oldversion) { // Moodle v2.5.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2013061101) { + // Define field markingworkflow to be added to assign + $table = new xmldb_table('assign'); + $field = new xmldb_field('markingworkflow', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'maxattempts'); - return true; -} + // Conditionally launch add field markingworkflow + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field markingallocation to be added to assign. + $field = new xmldb_field('markingallocation', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'markingworkflow'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field workflowstate to be added to assign_grades + $table = new xmldb_table('assign_user_flags'); + $field = new xmldb_field('workflowstate', XMLDB_TYPE_CHAR, '20', null, null, null, null, 'extensionduedate'); + // Conditionally launch add field workflowstate + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Define field allocatedmarker to be added to assign_grades. + $field = new xmldb_field('allocatedmarker', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'workflowstate'); + // Conditionally launch add field workflowstate + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_mod_savepoint(true, 2013061101, 'assign'); + } + + return true; +} diff --git a/mod/assign/gradingbatchoperationsform.php b/mod/assign/gradingbatchoperationsform.php index 21788ad0da027..98e16818cb28c 100644 --- a/mod/assign/gradingbatchoperationsform.php +++ b/mod/assign/gradingbatchoperationsform.php @@ -64,6 +64,12 @@ public function definition() { } } } + if ($instance['markingworkflow']) { + $options['setmarkingworkflowstate'] = get_string('setmarkingworkflowstate', 'assign'); + } + if ($instance['markingallocation']) { + $options['setmarkingallocation'] = get_string('setmarkingallocation', 'assign'); + } $mform->addElement('hidden', 'action', 'gradingbatchoperation'); $mform->setType('action', PARAM_ALPHA); diff --git a/mod/assign/gradingoptionsform.php b/mod/assign/gradingoptionsform.php index b8c38e612133e..8596aa713cfd7 100644 --- a/mod/assign/gradingoptionsform.php +++ b/mod/assign/gradingoptionsform.php @@ -54,7 +54,12 @@ public function definition() { if ($instance['submissionsenabled']) { $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options, $dirtyclass); } - + if (!empty($instance['markingallocationopt'])) { + $mform->addElement('select', 'markerfilter', get_string('markerfilter', 'assign'), $instance['markingallocationopt'], $dirtyclass); + } + if (!empty($instance['markingworkflowopt'])) { + $mform->addElement('select', 'workflowfilter', get_string('workflowfilter', 'assign'), $instance['markingworkflowopt'], $dirtyclass); + } // Quickgrading. if ($instance['showquickgrading']) { $mform->addElement('checkbox', 'quickgrading', get_string('quickgrading', 'assign'), '', $dirtyclass); diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index 57df5a04c86cb..00f9bf5f8f141 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -78,7 +78,7 @@ public function __construct(assign $assignment, $rowoffset, $quickgrading, $downloadfilename = null) { - global $CFG, $PAGE, $DB; + global $CFG, $PAGE, $DB, $USER; parent::__construct('mod_assign_grading'); $this->assignment = $assignment; @@ -135,7 +135,9 @@ public function __construct(assign $assignment, $fields .= 'g.timecreated as firstmarked, '; $fields .= 'uf.mailed as mailed, '; $fields .= 'uf.locked as locked, '; - $fields .= 'uf.extensionduedate as extensionduedate'; + $fields .= 'uf.extensionduedate as extensionduedate, '; + $fields .= 'uf.workflowstate as workflowstate, '; + $fields .= 'uf.allocatedmarker as allocatedmarker '; $submissionmaxattempt = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt FROM {assign_submission} mxs @@ -183,6 +185,39 @@ public function __construct(assign $assignment, } } + if ($this->assignment->get_instance()->markingallocation) { + if (has_capability('mod/assign:manageallocations', $this->assignment->get_context())) { + //Check to see if marker filter is set + $markerfilter = (int)get_user_preferences('assign_markerfilter', ''); + if (!empty($markerfilter)) { + $where .= ' AND uf.allocatedmarker = :markerid'; + $params['markerid'] = $markerfilter; + } + } else { // Only show users allocated to this marker. + $where .= ' AND uf.allocatedmarker = :markerid'; + $params['markerid'] = $USER->id; + } + } + + if ($this->assignment->get_instance()->markingworkflow) { + $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user(); + if (!empty($workflowstates)) { + $workflowfilter = get_user_preferences('assign_workflowfilter', ''); + if (array_key_exists($workflowfilter, $workflowstates) || $workflowfilter == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED) { + if ($workflowfilter == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED) { + $where .= ' AND (uf.workflowstate = :workflowstate OR uf.workflowstate IS NULL OR '. + $DB->sql_isempty('assign_user_flags', 'workflowstate', true, true).')'; + echo $where; + $params['workflowstate'] = $workflowfilter; + } else { + $where .= ' AND uf.workflowstate = :workflowstate'; + $params['workflowstate'] = $workflowfilter; + echo $where; + } + } + } + } + $this->set_sql($fields, $from, $where, $params); if ($downloadfilename) { @@ -228,6 +263,10 @@ public function __construct(assign $assignment, if ($assignment->is_any_submission_plugin_enabled()) { $columns[] = 'status'; $headers[] = get_string('status'); + } else if ($this->assignment->get_instance()->markingworkflow) { + $columns[] = 'workflowstatus'; + $headers[] = get_string('status'); + } // Team submission columns. @@ -238,7 +277,13 @@ public function __construct(assign $assignment, $columns[] = 'teamstatus'; $headers[] = get_string('teamsubmissionstatus', 'assign'); } - + //allocated marker + if ($this->assignment->get_instance()->markingallocation && + has_capability('mod/assign:manageallocations', $this->assignment->get_context())) { + // Add a column for the allocated marker. + $columns[] = 'allocatedmarker'; + $headers[] = get_string('marker', 'assign'); + } // Grade. $columns[] = 'grade'; $headers[] = get_string('grade'); @@ -251,6 +296,15 @@ public function __construct(assign $assignment, $columns[] = 'scale'; $headers[] = get_string('scale', 'assign'); } + + if ($this->assignment->get_instance()->markingworkflow) { + // Add a column for the marking workflow state. + $columns[] = 'workflowstate'; + $headers[] = get_string('markingworkflowstate', 'assign'); + } + // Add a column for the list of valid marking workflow states. + $columns[] = 'gradecanbechanged'; + $headers[] = get_string('gradecanbechanged', 'assign'); } if (!$this->is_downloading()) { // We have to call this column userid so we can use userid as a default sortable column. @@ -413,6 +467,89 @@ public function get_rows_per_page() { return $this->perpage; } + /** + * list current marking workflow state + * + * @param stdClass $row + * @return string + */ + public function col_workflowstatus(stdClass $row) { + $o = ''; + + $gradingdisabled = $this->assignment->grading_disabled($row->id); + if ($this->assignment->get_instance()->markingworkflow) { + // The function in the assignment keeps a static cache of this list of states. + $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user(); + $workflowstate = $row->workflowstate; + if (empty($workflowstate)) { + $workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED; + } + if ($this->quickgrading && !$gradingdisabled) { + $name = 'quickgrade_' . $row->id . '_workflowstate'; + $o .= html_writer::select($workflowstates, $name, $workflowstate, array('' => get_string('markingworkflowstatenotmarked', 'assign'))); + } else { + $o .= $this->output->container(get_string('markingworkflowstate' . $workflowstate, 'assign'), $workflowstate); + } + } + return $o; + } + + /** + * For download only - list current marking workflow state + * + * @param stdClass $row - The row of data + * @return string The current marking workflow state + */ + public function col_workflowstate($row) { + $state = $row->workflowstate; + if (empty($state)) { + $state = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED; + } + + return get_string('markingworkflowstate' . $state, 'assign'); + } + + /** + * list current marker + * + * @param stdClass $row - The row of data + * @return id the user->id of the marker. + */ + function col_allocatedmarker(stdClass $row) { + static $markers = null; + static $markerlist = array(); + if ($markers === null) { + $markers = get_users_by_capability($this->assignment->get_context(), 'mod/assign:grade'); + $markerlist[0] = get_string('choosemarker', 'assign'); + foreach ($markers as $marker) { + $markerlist[$marker->id] = fullname($marker); + } + } + if (empty($markerlist)) { + // TODO: add some form of notification here that no markers are available. + return ''; + } + if ($this->is_downloading()) { + return $markers[$row->allocatedmarker]; + } + + if ($this->quickgrading && has_capability('mod/assign:manageallocations', $this->assignment->get_context()) && + (empty($row->workflowstate) || + $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INMARKING || + $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED)) { + + $name = 'quickgrade_' . $row->id . '_allocatedmarker'; + return html_writer::select($markerlist, $name, $row->allocatedmarker, false); + } else if (!empty($row->allocatedmarker)) { + $output = ''; + if ($this->quickgrading) { // Add hidden field for quickgrading page. + $name = 'quickgrade_' . $row->id . '_allocatedmarker'; + $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$row->allocatedmarker)); + } + $output .= $markerlist[$row->allocatedmarker]; + return $output; + } + } /** * For download only - list all the valid options for this custom scale. * @@ -627,6 +764,21 @@ private function get_gradebook_data_for_user($userid) { * @param stdClass $row * @return string */ + public function col_gradecanbechanged(stdClass $row) { + $gradingdisabled = $this->assignment->grading_disabled($row->id); + if ($gradingdisabled) { + return get_string('no'); + } else { + return get_string('yes'); + } + } + + /** + * Format a column of data for display + * + * @param stdClass $row + * @return string + */ public function col_grademax(stdClass $row) { return format_float($this->assignment->get_instance()->grade, 2); } @@ -641,7 +793,7 @@ public function col_grade(stdClass $row) { $o = ''; $link = ''; - $separator = ''; + $separator = $this->output->spacer(array(), true); $grade = ''; $gradingdisabled = $this->assignment->grading_disabled($row->id); @@ -659,14 +811,15 @@ public function col_grade(stdClass $row) { 'action'=>'grade'); $url = new moodle_url('/mod/assign/view.php', $urlparams); $link = $this->output->action_link($url, $icon); - $separator = $this->output->spacer(array(), true); + $grade .= $link . $separator; } - $grade = $this->display_grade($row->grade, - $this->quickgrading && !$gradingdisabled, - $row->userid, - $row->timemarked); - return $link . $separator . $grade; + $grade .= $this->display_grade($row->grade, + $this->quickgrading && !$gradingdisabled, + $row->userid, + $row->timemarked); + + return $grade; } /** @@ -752,9 +905,7 @@ public function col_status(stdClass $row) { $lockedstr = get_string('submissionslockedshort', 'assign'); $o .= $this->output->container($lockedstr, 'lockedsubmission'); } - if ($row->grade !== null && $row->grade >= 0) { - $o .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded'); - } + $o .= $this->col_workflowstatus($row); if (!$row->timesubmitted) { $now = time(); $due = $instance->duedate; diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php index 81f9e40adbd9a..f243a24797c3b 100644 --- a/mod/assign/lang/en/assign.php +++ b/mod/assign/lang/en/assign.php @@ -29,6 +29,8 @@ $string['addnewattempt_help'] = 'This will create a new blank submission for you to work on.'; $string['addnewattemptfromprevious'] = 'Add a new attempt based on previous submission'; $string['addnewattemptfromprevious_help'] = 'This will copy the contents of your previous submission to a new submission for you to work on.'; +$string['allocatedmarker'] = 'Allocated Marker'; +$string['allocatedmarker_help'] = 'Marker allocated to this submission'; $string['allowsubmissions'] = 'Allow the user to continue making submissions to this assignment.'; $string['allowsubmissionsshort'] = 'Allow submission changes'; $string['allowsubmissionsfromdate'] = 'Allow submissions from'; @@ -42,7 +44,11 @@ $string['assign:exportownsubmission'] = 'Export own submission'; $string['assign:grade'] = 'Grade assignment'; $string['assign:grantextension'] = 'Grant extension'; +$string['assign:manageallocations'] = 'Manage markers allocated to submissions'; +$string['assign:managegrades'] = 'Review and release grades'; +$string['assign:releasegrades'] = 'Release grades'; $string['assign:revealidentities'] = 'Reveal student identities'; +$string['assign:reviewgrades'] = 'Review grades'; $string['assign:submit'] = 'Submit assignment'; $string['assign:view'] = 'View assignment'; $string['assignfeedback'] = 'Feedback plugin'; @@ -81,13 +87,18 @@ $string['batchoperationconfirmunlock'] = 'Unlock all selected submissions?'; $string['batchoperationconfirmreverttodraft'] = 'Revert selected submissions to draft?'; $string['batchoperationconfirmaddattempt'] = 'Allow another attempt for selected submissions?'; +$string['batchoperationconfirmsetmarkingworkflowstate'] = 'Set marking workflow state for all selected submissions?'; +$string['batchoperationconfirmsetmarkingallocation'] = 'Set marking allocation for all selected submissions?'; $string['batchoperationlock'] = 'lock submissions'; $string['batchoperationunlock'] = 'unlock submissions'; $string['batchoperationreverttodraft'] = 'revert submissions to draft'; +$string['batchsetallocatedmarker'] = 'Set allocated marker for {$a} selected user(s).'; +$string['batchsetmarkingworkflowstateforusers'] = 'Set marking workflow state for {$a} selected user(s).'; $string['blindmarking'] = 'Blind marking'; $string['blindmarking_help'] = 'Blind marking hides the identity of students to markers. Blind marking settings will be locked once a submission or grade has been made in relation to this assignment.'; $string['changegradewarning'] = 'This assignment has graded submissions and changing the grade will not automatically re-calculate existing submission grades. You must re-grade all existing submissions, if you wish to change the grade.'; $string['choosegradingaction'] = 'Grading action'; +$string['choosemarker'] = 'Choose...'; $string['chooseoperation'] = 'Choose operation'; $string['comment'] = 'Comment'; $string['completionsubmit'] = 'Student must submit to this activity to complete it'; @@ -132,6 +143,7 @@ $string['extensionduedate'] = 'Extension due date'; $string['extensionnotafterduedate'] = 'Extension date must be after the due date'; $string['extensionnotafterfromdate'] = 'Extension date must be after the allow submissions from date'; +$string['gradecanbechanged'] = 'Grade can be changed'; $string['gradersubmissionupdatedtext'] = '{$a->username} has updated their assignment submission for \'{$a->assignment}\' at {$a->timeupdated} @@ -199,9 +211,24 @@ $string['locksubmissions'] = 'Lock submissions'; $string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins'; $string['manageassignsubmissionplugins'] = 'Manage assignment submission plugins'; +$string['marker'] = 'Marker'; +$string['markerfilter'] = 'Marker filter'; +$string['markingallocation'] = 'Use marking allocation'; +$string['markingallocation_help'] = 'If enabled, allow markers to be allocated to individual students - requires marking workflow to be enabled.'; +$string['markingworkflow'] = 'Use marking workflow'; +$string['markingworkflow_help'] = 'If enabled, marks will go through a series of workflow stages before being released to students. This allows for multiple rounds of marking allows all the marks to be released to all students at one time.'; +$string['markingworkflowstate'] = 'Marking workflow state'; +$string['markingworkflowstate_help'] = 'The list of workflow states you can choose are controlled by your permissions in this assignment. The full list of valid states is:
  • In marking - The submissions is currently being marked
...'; +$string['markingworkflowstateinmarking'] = 'In marking'; +$string['markingworkflowstateinreview'] = 'In review'; +$string['markingworkflowstatenotmarked'] = 'Not marked'; +$string['markingworkflowstatereadyforreview'] = 'Marking completed'; +$string['markingworkflowstatereadyforrelease'] = 'Ready for release'; +$string['markingworkflowstatereleased'] = 'Released'; $string['maxattempts'] = 'Maximum attempts'; $string['maxattempts_help'] = 'The maximum number of submissions attempts that can be made by a student. After this number of attempts has been made the student's submission will not be able to be reopened.'; $string['maxgrade'] = 'Maximum grade'; +$string['maxgrade'] = 'Maximum Grade'; $string['messageprovider:assign_notification'] = 'Assignment notifications'; $string['modulename'] = 'Assignment'; $string['modulename_help'] = 'The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback. @@ -211,6 +238,7 @@ When reviewing assignments, teachers can leave feedback comments and upload files, such as marked-up student submissions, documents with comments or spoken audio feedback. Assignments can be graded using a numerical or custom scale or an advanced grading method such as a rubric. Final grades are recorded in the gradebook.'; $string['modulename_link'] = 'mod/assignment/view'; $string['modulenameplural'] = 'Assignments'; +$string['moreusers'] = '{$a} more...'; $string['mysubmission'] = 'My submission: '; $string['newsubmissions'] = 'Assignments submitted'; $string['noattempt'] = 'No attempt'; @@ -275,6 +303,11 @@ $string['sendlatenotifications_help'] = 'If enabled, graders (usually teachers) receive a message whenever a student submits an assignment late. Message methods are configurable.'; $string['sendsubmissionreceipts'] = 'Send submission receipt to students'; $string['sendsubmissionreceipts_help'] = 'This switch will enable submission receipts for students. Students will receive a notification every time they successfully submit an assignment'; +$string['setmarkingallocation'] = 'Set allocated marker'; +$string['setmarkingworkflowstate'] = 'Set marking workflow state'; +$string['selectedusers'] = 'Selected users'; +$string['setmarkingworkflowstateforlog'] = 'Set marking workflow state : (id={$a->id}, fullname={$a->fullname}, state={$a->state}). '; +$string['setmarkerallocationforlog'] = 'Set marking allocation : (id={$a->id}, fullname={$a->fullname}, marker={$a->marker}). '; $string['settings'] = 'Assignment settings'; $string['showrecentsubmissions'] = 'Show recent submissions'; $string['submissioncopiedtext'] = 'You have made a copy of your previous @@ -351,6 +384,9 @@ $string['userextensiondate'] = 'Extension granted until: {$a}'; $string['userswhoneedtosubmit'] = 'Users who need to submit: {$a}'; $string['usergrade'] = 'User grade'; +$string['validmarkingworkflowstates'] = 'Valid marking workflow states'; +$string['viewbatchsetmarkingworkflowstate'] = 'View batch set marking workflow state page.'; +$string['viewbatchmarkingallocation'] = 'View batch set marking allocation page.'; $string['viewfeedback'] = 'View feedback'; $string['viewfeedbackforuser'] = 'View feedback for user: {$a}'; $string['viewfullgradingpage'] = 'Open the full grading page to provide feedback'; @@ -365,7 +401,7 @@ $string['viewsummary'] = 'View summary'; $string['viewsubmissiongradingtable'] = 'View submission grading table.'; $string['viewrevealidentitiesconfirm'] = 'View reveal student identities confirmation page.'; +$string['workflowfilter'] = 'Workflow filter'; $string['submissiontypes'] = 'Submission types'; $string['feedbacktypes'] = 'Feedback types'; $string['groupsubmissionsettings'] = 'Group submission settings'; - diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 2267c9678fc0f..3f307d37e266a 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -44,6 +44,14 @@ // Special value means allow unlimited attempts. define('ASSIGN_UNLIMITED_ATTEMPTS', -1); +// Marking workflow states. +define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked'); +define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking'); +define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview'); +define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview'); +define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease'); +define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released'); + require_once($CFG->libdir . '/accesslib.php'); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->dirroot . '/repository/lib.php'); @@ -111,6 +119,9 @@ class assign { /** @var string modulenameplural prevents excessive calls to get_string */ private static $modulenameplural = null; + /** @var array of marking workflow states for the current user */ + private $markingworkflowstates = null; + /** * Constructor for the base assign class. * @@ -389,6 +400,14 @@ public function view($action='') { $this->process_unlock(); $action = 'redirect'; $nextpageparams['action'] = 'grading'; + } else if ($action == 'setbatchmarkingworkflowstate') { + $this->process_set_batch_marking_workflow_state(); + $action = 'redirect'; + $nextpageparams['action'] = 'grading'; + } else if ($action == 'setbatchmarkingallocation') { + $this->process_set_batch_marking_allocation(); + $action = 'redirect'; + $nextpageparams['action'] = 'grading'; } else if ($action == 'confirmsubmit') { $action = 'submit'; if ($this->process_submit_for_grading($mform)) { @@ -490,6 +509,10 @@ public function view($action='') { $o .= $this->view_plugin_page(); } else if ($action == 'viewcourseindex') { $o .= $this->view_course_index(); + } else if ($action == 'viewbatchsetmarkingworkflowstate') { + $o .= $this->view_batch_set_workflow_state($mform); + } else if ($action == 'viewbatchmarkingallocation') { + $o .= $this->view_batch_markingallocation($mform); } else { $o .= $this->view_submission_page(); } @@ -540,6 +563,8 @@ public function add_instance(stdClass $formdata, $callplugins) { if (!empty($formdata->maxattempts)) { $update->maxattempts = $formdata->maxattempts; } + $update->markingworkflow = $formdata->markingworkflow; + $update->markingallocation = $formdata->markingallocation; $returnid = $DB->insert_record('assign', $update); $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST); @@ -863,6 +888,8 @@ public function update_instance($formdata) { if (!empty($formdata->maxattempts)) { $update->maxattempts = $formdata->maxattempts; } + $update->markingworkflow = $formdata->markingworkflow; + $update->markingallocation = $formdata->markingallocation; $result = $DB->update_record('assign', $update); $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST); @@ -1677,6 +1704,13 @@ public function update_grade($grade) { $grade->timemodified = time(); + if (!empty($grade->workflowstate)) { + $validstates = $this->get_marking_workflow_states_for_current_user(); + if (!array_key_exists($grade->workflowstate, $validstates)) { + return false; + } + } + if ($grade->grade && $grade->grade != -1) { if ($this->get_instance()->grade > 0) { if (!is_numeric($grade->grade)) { @@ -2477,6 +2511,8 @@ public function get_user_flags($userid, $create) { $flags->userid = $userid; $flags->locked = 0; $flags->extensionduedate = 0; + $flags->workflowstate = ''; + $flags->allocatedmarker = 0; // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet. // This is because students only want to be notified about certain types of update (grades and feedback). @@ -2696,6 +2732,12 @@ protected function view_single_grade_page($mform) { if ($grade->grade !== null && $grade->grade >= 0) { $data->grade = format_float($grade->grade, 2); } + if (!empty($flags->workflowstate)) { + $data->workflowstate = $flags->workflowstate; + } + if (!empty($flags->allocatedmarker)) { + $data->allocatedmarker = $flags->allocatedmarker; + } } else { $data = new stdClass(); $data->grade = ''; @@ -2853,17 +2895,42 @@ protected function view_grading_table() { $perpage = get_user_preferences('assign_perpage', 10); $filter = get_user_preferences('assign_filter', ''); + $markerfilter = get_user_preferences('assign_markerfilter', ''); + $workflowfilter = get_user_preferences('assign_workflowfilter', ''); $controller = $gradingmanager->get_active_controller(); $showquickgrading = empty($controller); $quickgrading = get_user_preferences('assign_quickgrading', false); + $markingallocation = $this->get_instance()->markingallocation && + has_capability('mod/assign:manageallocations', $this->context); + // Get markers to use in drop lists. + $markingallocationoptions = array(); + if ($markingallocation) { + $markers = get_users_by_capability($this->context, 'mod/assign:grade'); + $markingallocationoptions[''] = get_string('filternone', 'assign'); + foreach ($markers as $marker) { + $markingallocationoptions[$marker->id] = fullname($marker); + } + } + + $markingworkflow = $this->get_instance()->markingworkflow; + // Get marking states to show in form. + $markingworkflowoptions = array(); + if ($markingworkflow) { + $markingworkflowoptions[''] = get_string('filternone', 'assign'); + $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = get_string('markingworkflowstatenotmarked', 'assign'); + $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user()); + } + // Print options for changing the filter and changing the number of results per page. $gradingoptionsformparams = array('cm'=>$cmid, 'contextid'=>$this->context->id, 'userid'=>$USER->id, 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(), 'showquickgrading'=>$showquickgrading, - 'quickgrading'=>$quickgrading); + 'quickgrading'=>$quickgrading, + 'markingworkflowopt'=>$markingworkflowoptions, + 'markingallocationopt'=>$markingallocationoptions); $classoptions = array('class'=>'gradingoptionsform'); $gradingoptionsform = new mod_assign_grading_options_form(null, @@ -2877,7 +2944,9 @@ protected function view_grading_table() { 'duedate'=>$this->get_instance()->duedate, 'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod, 'feedbackplugins'=>$this->get_feedback_plugins(), - 'context'=>$this->get_context()); + 'context'=>$this->get_context(), + 'markingworkflow'=>$markingworkflow, + 'markingallocation'=>$markingallocation); $classoptions = array('class'=>'gradingbatchoperationsform'); $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null, @@ -2889,6 +2958,8 @@ protected function view_grading_table() { $gradingoptionsdata = new stdClass(); $gradingoptionsdata->perpage = $perpage; $gradingoptionsdata->filter = $filter; + $gradingoptionsdata->markerfilter = $markerfilter; + $gradingoptionsdata->workflowfilter = $workflowfilter; $gradingoptionsform->set_data($gradingoptionsdata); $actionformtext = $this->get_renderer()->render($gradingactions); @@ -3149,12 +3220,17 @@ protected function process_grading_batch_operation(& $mform) { require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php'); require_sesskey(); + $markingallocation = $this->get_instance()->markingallocation && + has_capability('mod/assign:manageallocations', $this->context); + $batchformparams = array('cm'=>$this->get_course_module()->id, 'submissiondrafts'=>$this->get_instance()->submissiondrafts, 'duedate'=>$this->get_instance()->duedate, 'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod, 'feedbackplugins'=>$this->get_feedback_plugins(), - 'context'=>$this->get_context()); + 'context'=>$this->get_context(), + 'markingworkflow'=>$this->get_instance()->markingworkflow, + 'markingallocation'=>$markingallocation); $formclasses = array('class'=>'gradingbatchoperationsform'); $mform = new mod_assign_grading_batch_operations_form(null, $batchformparams, @@ -3173,6 +3249,10 @@ protected function process_grading_batch_operation(& $mform) { // Reset the form so the grant extension page will create the extension form. $mform = null; return 'grantextension'; + } else if ($data->operation == 'setmarkingworkflowstate') { + return 'viewbatchsetmarkingworkflowstate'; + } else if ($data->operation == 'setmarkingallocation') { + return 'viewbatchmarkingallocation'; } else if (strpos($data->operation, $prefix) === 0) { $tail = substr($data->operation, strlen($prefix)); list($plugintype, $action) = explode('_', $tail, 2); @@ -3205,6 +3285,120 @@ protected function process_grading_batch_operation(& $mform) { return 'grading'; } + /** + * Shows a form that allows the workflow state for selected submissions to be changed. + * + * @param moodleform $mform Set to a grading batch operations form + * @return string - the page to view after processing these actions + */ + private function view_batch_set_workflow_state($mform) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/assign/batchsetmarkingworkflowstateform.php'); + + $o = ''; + + $submitteddata = $mform->get_data(); + $users = $submitteddata->selectedusers; + $userlist = explode(',', $users); + + $formparams = array('cm'=>$this->get_course_module()->id, + 'users'=>$userlist, + 'context'=>$this->get_context()); + + $usershtml = ''; + + $usercount = 0; + $extrauserfields = get_extra_user_fields($this->get_context()); + foreach ($userlist as $userid) { + if ($usercount >= 5) { + $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5); + break; + } + $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST); + + $usershtml .= $this->get_renderer()->render(new assign_user_summary($user, + $this->get_course()->id, + has_capability('moodle/site:viewfullnames', + $this->get_course_context()), + $this->is_blind_marking(), + $this->get_uniqueid_for_user($user->id), + $extrauserfields)); + $usercount += 1; + } + + $formparams['usershtml'] = $usershtml; + $formparams['markingworkflowstates'] = $this->get_marking_workflow_states_for_current_user(); + + $mform = new mod_assign_batch_set_marking_workflow_state_form(null, $formparams); + $o .= $this->get_renderer()->header(); + $o .= $this->get_renderer()->render(new assign_form('setworkflowstate', $mform)); + $o .= $this->view_footer(); + + $this->add_to_log('view batch set marking workflow state', get_string('viewbatchsetmarkingworkflowstate', 'assign')); + return $o; + } + + /** + * Shows a form that allows the allocated marker for selected submissions to be changed. + * + * @param moodleform $mform Set to a grading batch operations form + * @return string - the page to view after processing these actions + */ + private function view_batch_markingallocation($mform) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/assign/batchsetallocatedmarkerform.php'); + + $o = ''; + + $submitteddata = $mform->get_data(); + $users = $submitteddata->selectedusers; + $userlist = explode(',', $users); + + $formparams = array('cm'=>$this->get_course_module()->id, + 'users'=>$userlist, + 'context'=>$this->get_context()); + + $usershtml = ''; + + $usercount = 0; + $extrauserfields = get_extra_user_fields($this->get_context()); + foreach ($userlist as $userid) { + if ($usercount >= 5) { + $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5); + break; + } + $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST); + + $usershtml .= $this->get_renderer()->render(new assign_user_summary($user, + $this->get_course()->id, + has_capability('moodle/site:viewfullnames', + $this->get_course_context()), + $this->is_blind_marking(), + $this->get_uniqueid_for_user($user->id), + $extrauserfields)); + $usercount += 1; + } + + $formparams['usershtml'] = $usershtml; + $markers = get_users_by_capability($this->get_context(), 'mod/assign:grade'); + $markerlist = array(); + foreach ($markers as $marker) { + $markerlist[$marker->id] = fullname($marker); + } + + $formparams['markers'] = $markerlist; + + $mform = new mod_assign_batch_set_allocatedmarker_form(null, $formparams); + $o .= $this->get_renderer()->header(); + $o .= $this->get_renderer()->render(new assign_form('setworkflowstate', $mform)); + $o .= $this->view_footer(); + + $this->add_to_log('view batch set marker allocation', get_string('viewbatchmarkingallocation', 'assign')); + return $o; + } + /** * Ask the user to confirm they want to submit their work for grading. * @@ -3296,7 +3490,7 @@ public function view_student_summary($user, $showlinks) { ($this->is_any_submission_plugin_enabled()) && $showlinks; - $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($user->id); + $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($user->id, false); // Grading criteria preview. $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions'); @@ -3381,9 +3575,16 @@ public function view_student_summary($user, $showlinks) { } } + $gradereleased = true; + if ($this->get_instance()->markingworkflow && + (empty($grade) || $flags->workflowstate != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED)) { + $gradereleased = false; + $emptyplugins = true; // Don't show feedback plugins until released either. + } + $cangrade = has_capability('mod/assign:grade', $this->get_context()); // If there is feedback or a visible grade, show the summary. - if ((!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) || + if ((!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden) && $gradereleased) || !$emptyplugins) { $gradefordisplay = null; @@ -3654,6 +3855,14 @@ protected function gradebook_item_update($submission=null, $grade=null) { if ($this->is_blind_marking()) { return false; } + // If marking workflow is enabled and grade is not released then don't send to gradebook yet. + if ($this->get_instance()->markingworkflow && !empty($grade)) { + $flags = $this->get_user_flags($grade->userid, false); + if (empty($flags->workflowstate) || $flags->workflowstate != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) { + return false; + } + } + if ($submission != null) { if ($submission->userid == 0) { // This is a group submission update. @@ -3846,7 +4055,7 @@ public function submissions_open($userid = 0) { } } - if ($this->grading_disabled($userid)) { + if ($this->grading_disabled($userid, false)) { return false; } @@ -4367,6 +4576,8 @@ protected function process_save_quick_grades() { $record->userid = $userid; if ($modified >= 0) { $record->grade = unformat_float(optional_param('quickgrade_' . $record->userid, -1, PARAM_TEXT)); + $record->workflowstate = optional_param('quickgrade_' . $record->userid.'_workflowstate', '', PARAM_TEXT); + $record->allocatedmarker = optional_param('quickgrade_' . $record->userid.'_allocatedmarker', '', PARAM_INT); } else { // This user was not in the grading table. continue; @@ -4389,13 +4600,14 @@ protected function process_save_quick_grades() { FROM {assign_grades} mxg WHERE mxg.assignment = :assignid1 GROUP BY mxg.userid'; - $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified + $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified, uf.workflowstate, uf.allocatedmarker FROM {user} u LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid LEFT JOIN {assign_grades} g ON u.id = g.userid AND g.assignment = :assignid2 AND g.attemptnumber = gmx.maxattempt + LEFT JOIN {assign_user_flags} uf ON uf.assignment = g.assignment AND uf.userid = g.userid WHERE u.id ' . $userids; $currentgrades = $DB->get_recordset_sql($sql, $params); @@ -4435,13 +4647,16 @@ protected function process_save_quick_grades() { if (($current->grade < 0 || $current->grade === null) && ($modified->grade < 0 || $modified->grade === null)) { // Different ways to indicate no grade. - continue; + $modified->grade = $current->grade; // Keep existing grade. } // Treat 0 and null as different values. if ($current->grade !== null) { $current->grade = floatval($current->grade); } - if ($current->grade !== $modified->grade) { + if ($current->grade !== $modified->grade || + ($this->get_instance()->markingallocation && $current->allocatedmarker != $modified->allocatedmarker ) || + ($this->get_instance()->markingworkflow && $current->workflowstate !== $modified->workflowstate )) { + // Grade changed. if ($this->grading_disabled($modified->userid)) { continue; @@ -4463,6 +4678,7 @@ protected function process_save_quick_grades() { // Ok - ready to process the updates. foreach ($modifiedusers as $userid => $modified) { $grade = $this->get_user_grade($userid, true); + $flags = $this->get_user_flags($userid, true); $grade->grade= grade_floatval(unformat_float($modified->grade)); $grade->grader= $USER->id; @@ -4479,6 +4695,13 @@ protected function process_save_quick_grades() { } $this->update_grade($grade); + if ($flags->workflowstate != $modified->workflowstate || + $flags->allocatedmarker != $modified->allocatedmarker) { + + $flags->workflowstate = $modified->workflowstate; + $flags->allocatedmarker = $modified->allocatedmarker; + $this->update_user_flags($flags); + } $this->notify_grade_modified($grade); // Save outcomes. @@ -4575,12 +4798,34 @@ protected function process_save_grading_options() { $controller = $gradingmanager->get_active_controller(); $showquickgrading = empty($controller); + $markingallocation = $this->get_instance()->markingallocation && + has_capability('mod/assign:manageallocations', $this->context); + // Get markers to use in drop lists. + $markingallocationoptions = array(); + if ($markingallocation) { + $markingallocationoptions[''] = get_string('filternone', 'assign'); + $markers = get_users_by_capability($this->context, 'mod/assign:grade'); + foreach ($markers as $marker) { + $markingallocationoptions[$marker->id] = fullname($marker); + } + } + + // Get marking states to show in form. + $markingworkflowoptions = array(); + if ($this->get_instance()->markingworkflow) { + $markingworkflowoptions[''] = get_string('filternone', 'assign'); + $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = get_string('markingworkflowstatenotmarked', 'assign'); + $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user()); + } + $gradingoptionsparams = array('cm'=>$this->get_course_module()->id, 'contextid'=>$this->context->id, 'userid'=>$USER->id, 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(), 'showquickgrading'=>$showquickgrading, - 'quickgrading'=>false); + 'quickgrading'=>false, + 'markingworkflowopt' => $markingworkflowoptions, + 'markingallocationopt' => $markingallocationoptions); $mform = new mod_assign_grading_options_form(null, $gradingoptionsparams); if ($formdata = $mform->get_data()) { @@ -4588,6 +4833,12 @@ protected function process_save_grading_options() { if (isset($formdata->filter)) { set_user_preference('assign_filter', $formdata->filter); } + if (isset($formdata->markerfilter)) { + set_user_preference('assign_markerfilter', $formdata->markerfilter); + } + if (isset($formdata->workflowfilter)) { + set_user_preference('assign_workflowfilter', $formdata->workflowfilter); + } if ($showquickgrading) { set_user_preference('assign_quickgrading', isset($formdata->quickgrading)); } @@ -4843,11 +5094,18 @@ protected function process_save_submission(&$mform, &$notices) { * Determine if this users grade is locked or overridden. * * @param int $userid - The student userid + * @param bool $checkworkflow - whether to include a check for the workflow state. * @return bool $gradingdisabled */ - public function grading_disabled($userid) { + public function grading_disabled($userid, $checkworkflow=true) { global $CFG; - + if ($checkworkflow && $this->get_instance()->markingworkflow) { + $grade = $this->get_user_grade($userid, false); + $validstates = $this->get_marking_workflow_states_for_current_user(); + if (!empty($grade) && !empty($grade->workflowstate) && !array_key_exists($grade->workflowstate, $validstates)) { + return true; + } + } $gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', @@ -5035,6 +5293,27 @@ public function add_grade_form_elements(MoodleQuickForm $mform, stdClass $data, } $gradestring = $usergrade; } + + if ($this->get_instance()->markingworkflow) { + $options = array('' => get_string('markingworkflowstatenotmarked', 'assign')) + $this->get_marking_workflow_states_for_current_user(); + $mform->addElement('select', 'workflowstate', get_string('markingworkflowstate', 'assign'), $options); + $mform->addHelpButton('workflowstate', 'markingworkflowstate', 'assign'); + } + + if ($this->get_instance()->markingallocation && has_capability('mod/assign:manageallocations', $this->context)) { + $markers = get_users_by_capability($this->context, 'mod/assign:grade'); + $markerlist = array('' => get_string('choosemarker', 'assign')); + foreach ($markers as $marker) { + $markerlist[$marker->id] = fullname($marker); + } + $mform->addElement('select', 'allocatedmarker', get_string('allocatedmarker', 'assign'), $markerlist); + $mform->addHelpButton('allocatedmarker', 'allocatedmarker', 'assign'); + $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW); + $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW); + $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE); + $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED); + } + $mform->addElement('static', 'currentgrade', get_string('currentgrade', 'assign'), $gradestring); if (count($useridlist) > 1) { @@ -5310,6 +5589,89 @@ protected function process_lock($userid = 0) { $this->add_to_log('lock submission', $logmessage); } + + /** + * Set the workflow state for multiple users + * + * @return void + */ + private function process_set_batch_marking_workflow_state() { + global $DB; + + require_sesskey(); + + $batchusers = required_param('selectedusers', PARAM_TEXT); + $state = required_param('markingworkflowstate', PARAM_ALPHA); + $useridlist = explode(',', $batchusers); + + foreach ($useridlist as $userid) { + $flags = $this->get_user_flags($userid, true); + + $flags->workflowstate = $state; + + $gradingdisabled = $this->grading_disabled($userid); + + // Will not apply update if user does not have permission to assign this workflow state. + if (!$gradingdisabled && $this->update_user_flags($flags)) { + + $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); + + $params = array('id'=>$user->id, + 'fullname'=>fullname($user), + 'state'=>$state); + $message = get_string('setmarkingworkflowstateforlog', 'assign', $params); + $this->add_to_log('set marking workflow state', $message); + } + } + + + } + + /** + * Set the marking allocation for multiple users + * + * @return void + */ + private function process_set_batch_marking_allocation() { + global $DB; + + require_sesskey(); + require_capability('mod/assign:manageallocations', $this->context); + + $batchusers = required_param('selectedusers', PARAM_TEXT); + $markerid = required_param('allocatedmarker', PARAM_INT); + $marker = $DB->get_record('user', array('id' => $markerid), '*', MUST_EXIST); + + $useridlist = explode(',', $batchusers); + + foreach ($useridlist as $userid) { + $flags = $this->get_user_flags($userid, true); + if ($flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW || + $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW || + $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE || + $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) { + + continue; // Allocated marker can only be changed in certain workflow states. + } + + $flags->allocatedmarker = $marker->id; + + if ($this->update_user_flags($flags)) { + + $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); + + $params = array('id'=>$user->id, + 'fullname'=>fullname($user), + 'marker'=>fullname($marker)); + $message = get_string('setmarkerallocationforlog', 'assign', $params); + $this->add_to_log('set marking allocation', $message); + } + } + + + } + + /** * Unlock the process. * @@ -5363,6 +5725,12 @@ protected function apply_grade_to_user($formdata, $userid, $attemptnumber) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } + if (isset($formdata->workflowstate) || isset($formdata->allocatedmarker)) { + $flags = $this->get_user_flags($userid, true); + $flags->workflowstate = isset($formdata->workflowstate) ? $formdata->workflowstate : $flags->workflowstate; + $flags->allocatedmarker = isset($formdata->allocatedmarker) ? $formdata->allocatedmarker : $flags->allocatedmarker; + $this->update_user_flags($flags); + } } $grade->grader= $USER->id; @@ -5904,6 +6272,33 @@ public static function get_user_id_for_uniqueid_static($assignid, $uniqueid) { return false; } + /** + * Get the list of marking_workflow states the current user has permission to transition a grade to. + * + * @return array of state => description + */ + public function get_marking_workflow_states_for_current_user() { + if (!empty($this->markingworkflowstates)) { + return $this->markingworkflowstates; + } + $states = array(); + if (has_capability('mod/assign:grade', $this->context)) { + $states[ASSIGN_MARKING_WORKFLOW_STATE_INMARKING] = get_string('markingworkflowstateinmarking', 'assign'); + $states[ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW] = get_string('markingworkflowstatereadyforreview', 'assign'); + } + if (has_any_capability(array('mod/assign:reviewgrades', + 'mod/assign:managegrades'), $this->context)) { + $states[ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW] = get_string('markingworkflowstateinreview', 'assign'); + $states[ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE] = get_string('markingworkflowstatereadyforrelease', 'assign'); + } + if (has_any_capability(array('mod/assign:releasegrades', + 'mod/assign:managegrades'), $this->context)) { + $states[ASSIGN_MARKING_WORKFLOW_STATE_RELEASED] = get_string('markingworkflowstatereleased', 'assign'); + } + $this->markingworkflowstates = $states; + return $this->markingworkflowstates; + } + } /** diff --git a/mod/assign/mod_form.php b/mod/assign/mod_form.php index c5489d11c3fba..79c32393fa41f 100644 --- a/mod/assign/mod_form.php +++ b/mod/assign/mod_form.php @@ -179,6 +179,15 @@ public function definition() { $mform->freeze('blindmarking'); } + $mform->addElement('advcheckbox', 'markingworkflow', get_string('markingworkflow', 'assign'), '', null, array(0, 1)); + $mform->addHelpButton('markingworkflow', 'markingworkflow', 'assign'); + $mform->setDefault('markingworkflow', 0); + + $mform->addElement('advcheckbox', 'markingallocation', get_string('markingallocation', 'assign'), '', null, array(0, 1)); + $mform->addHelpButton('markingallocation', 'markingallocation', 'assign'); + $mform->setDefault('markingallocation', 0); + $mform->disabledIf('markingallocation', 'markingworkflow', 'notchecked'); + $this->standard_coursemodule_elements(); $this->apply_admin_defaults(); diff --git a/mod/assign/module.js b/mod/assign/module.js index 90a789c7526a1..37e340c57601e 100644 --- a/mod/assign/module.js +++ b/mod/assign/module.js @@ -131,6 +131,18 @@ M.mod_assign.init_grading_options = function(Y) { Y.one('form.gradingoptionsform').submit(); }); } + var markerfilterelement = Y.one('#id_markerfilter'); + if (markerfilterelement) { + markerfilterelement.on('change', function(e) { + Y.one('form.gradingoptionsform').submit(); + }); + } + var workflowfilterelement = Y.one('#id_workflowfilter'); + if (workflowfilterelement) { + workflowfilterelement.on('change', function(e) { + Y.one('form.gradingoptionsform').submit(); + }); + } var quickgradingelement = Y.one('#id_quickgrading'); if (quickgradingelement) { quickgradingelement.on('change', function(e) { diff --git a/mod/assign/renderer.php b/mod/assign/renderer.php index 01bd7a7338c15..bd86e300beb6a 100644 --- a/mod/assign/renderer.php +++ b/mod/assign/renderer.php @@ -957,6 +957,8 @@ public function render_assign_grading_table(assign_grading_table $table) { $this->page->requires->string_for_js('batchoperationconfirmreverttodraft', 'assign'); $this->page->requires->string_for_js('batchoperationconfirmunlock', 'assign'); $this->page->requires->string_for_js('batchoperationconfirmaddattempt', 'assign'); + $this->page->requires->string_for_js('batchoperationconfirmsetmarkingworkflowstate', 'assign'); + $this->page->requires->string_for_js('batchoperationconfirmsetmarkingallocation', 'assign'); $this->page->requires->string_for_js('editaction', 'assign'); foreach ($table->plugingradingbatchoperations as $plugin => $operations) { foreach ($operations as $operation => $description) { diff --git a/mod/assign/tests/generator/lib.php b/mod/assign/tests/generator/lib.php index 6c225819bfa94..ffe9b6abbf939 100644 --- a/mod/assign/tests/generator/lib.php +++ b/mod/assign/tests/generator/lib.php @@ -65,7 +65,9 @@ public function create_instance($record = null, array $options = null) { 'blindmarking' => 0, 'cmidnumber' => '', 'attemptreopenmethod' => 'none', - 'maxattempts' => -1 + 'maxattempts' => -1, + 'markingworkflow' => 0, + 'markingallocation' => 0, ); foreach ($defaultsettings as $name => $value) { diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php index c13d76c0807c2..ef32825bc98d7 100644 --- a/mod/assign/tests/locallib_test.php +++ b/mod/assign/tests/locallib_test.php @@ -743,5 +743,109 @@ public function test_attempt_reopen_method_manual() { } + public function test_markingworkflow() { + global $PAGE; + + $this->setUser($this->editingteachers[0]); + $assign = $this->create_instance(array('markingworkflow'=>1)); + $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id))); + + // Mark the submission and set to notmarked + $this->setUser($this->teachers[0]); + $data = new stdClass(); + $data->grade = '50.0'; + $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the student can't see the grade. + $this->setUser($this->students[0]); + $output = $assign->view_student_summary($this->students[0], true); + $this->assertEquals(false, strpos($output, '50.0')); + + // Mark the submission and set to inmarking + $this->setUser($this->teachers[0]); + $data = new stdClass(); + $data->grade = '50.0'; + $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_INMARKING; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the student can't see the grade. + $this->setUser($this->students[0]); + $output = $assign->view_student_summary($this->students[0], true); + $this->assertEquals(false, strpos($output, '50.0')); + + // Mark the submission and set to readyforreview + $this->setUser($this->teachers[0]); + $data = new stdClass(); + $data->grade = '50.0'; + $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the student can't see the grade. + $this->setUser($this->students[0]); + $output = $assign->view_student_summary($this->students[0], true); + $this->assertEquals(false, strpos($output, '50.0')); + + // Mark the submission and set to inreview + $this->setUser($this->teachers[0]); + $data = new stdClass(); + $data->grade = '50.0'; + $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the student can't see the grade. + $this->setUser($this->students[0]); + $output = $assign->view_student_summary($this->students[0], true); + $this->assertEquals(false, strpos($output, '50.0')); + + // Mark the submission and set to readyforrelease + $this->setUser($this->teachers[0]); + $data = new stdClass(); + $data->grade = '50.0'; + $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the student can't see the grade. + $this->setUser($this->students[0]); + $output = $assign->view_student_summary($this->students[0], true); + $this->assertEquals(false, strpos($output, '50.0')); + + // Mark the submission and set to released + $this->setUser($this->teachers[0]); + $data = new stdClass(); + $data->grade = '50.0'; + $data->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the student can see the grade. + $this->setUser($this->students[0]); + $output = $assign->view_student_summary($this->students[0], true); + $this->assertNotEquals(false, strpos($output, '50.0')); + } + + public function test_markerallocation() { + global $PAGE; + + $this->setUser($this->editingteachers[0]); + $assign = $this->create_instance(array('markingworkflow'=>1,'markingallocation'=>1)); + $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id))); + + // Allocate marker to submission + $data = new stdClass(); + $data->allocatedmarker = $this->teachers[0]->id; + $assign->testable_apply_grade_to_user($data, $this->students[0]->id, 0); + + // Check the allocated marker can view the submission + $this->setUser($this->teachers[0]); + $gradingtable = new assign_grading_table($assign, 100, '', 0, true); + $output = $assign->get_renderer()->render($gradingtable); + $this->assertEquals(true, strpos($output, $this->students[0]->lastname)); + + // Check that other teachers can't view this submission + $this->setUser($this->teachers[1]); + $gradingtable = new assign_grading_table($assign, 100, '', 0, true); + $output = $assign->get_renderer()->render($gradingtable); + $this->assertNotEquals(true, strpos($output, $this->students[0]->lastname)); + } } diff --git a/mod/assign/upgradelib.php b/mod/assign/upgradelib.php index e3899dda2fb1c..3b416a5567c41 100644 --- a/mod/assign/upgradelib.php +++ b/mod/assign/upgradelib.php @@ -90,6 +90,8 @@ public function upgrade_assignment($oldassignmentid, & $log) { $data->grade = $oldassignment->grade; $data->submissiondrafts = $oldassignment->resubmit; $data->requiresubmissionstatement = 0; + $data->markingworkflow = 0; + $data->markingallocation = 0; $data->cutoffdate = 0; // New way to specify no late submissions. if ($oldassignment->preventlate) { diff --git a/mod/assign/version.php b/mod/assign/version.php index 5d47e44413abf..249060aaa4ae3 100644 --- a/mod/assign/version.php +++ b/mod/assign/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics). -$module->version = 2013061100; // The current module version (Date: YYYYMMDDXX). +$module->version = 2013061101; // The current module version (Date: YYYYMMDDXX). $module->requires = 2013050100; // Requires this Moodle version. $module->cron = 60;