MDL-52954 assign: Rebuild the assignment single grade page.

This commit is contained in:
Damyon Wiese 2016-02-01 16:13:46 +08:00
parent 2a3647bae5
commit bb690849c9
86 changed files with 4593 additions and 279 deletions

View file

@ -651,7 +651,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
'name' => 'showmarkerdesc', 'name' => 'showmarkerdesc',
'value' => "true")+$checked1); 'value' => "true")+$checked1);
$radio1 = html_writer::tag('label', $radio1); $radio1 = html_writer::tag('label', $radio1);
$radio2 .= html_writer::tag('input', get_string('hidemarkerdesc', 'gradingform_guide'), array('type' => 'radio', $radio2 = html_writer::tag('input', get_string('hidemarkerdesc', 'gradingform_guide'), array('type' => 'radio',
'name' => 'showmarkerdesc', 'name' => 'showmarkerdesc',
'value' => "false")+$checked2); 'value' => "false")+$checked2);
$radio2 = html_writer::tag('label', $radio2); $radio2 = html_writer::tag('label', $radio2);

File diff suppressed because one or more lines are too long

1
lib/amd/build/tooltip.min.js vendored Normal file
View file

@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(b){this._regionSelector=b,a(this._regionSelector).each(function(b,c){var d=a(c).attr("aria-describedby");if(d){var e=document.getElementById(d);if(e){var f="tooltip"==a(e).attr("role");f&&(a(e).hide(),a(c).attr("tabindex","0")),a(c).on("focus",this._handleFocus.bind(this)),a(c).on("mouseover",this._handleMouseOver.bind(this)),a(c).on("mouseout",this._handleMouseOut.bind(this)),a(c).on("blur",this._handleBlur.bind(this)),a(c).on("keydown",this._handleKeyDown.bind(this))}}}.bind(this))};return b.prototype._regionSelector=null,b.prototype._showTooltip=function(b){var c=a(b.target),d=c.attr("aria-describedby");if(d){var e=a(document.getElementById(d));if(e.show(),e.attr("aria-hidden","false"),!e.is(".tooltip")){var f=a('<div class="tooltip-inner"></div>');f.append(e.contents()),e.append(f),e.addClass("tooltip"),e.addClass("bottom"),e.append('<div class="tooltip-arrow"></div>')}var g=c.offset();g.top+=c.height()+10,a(e).offset(g)}},b.prototype._hideTooltip=function(b){var c=a(b.target),d=c.attr("aria-describedby");if(d){var e=document.getElementById(d);a(e).hide(),a(e).attr("aria-hidden","true")}},b.prototype._handleFocus=function(a){this._showTooltip(a)},b.prototype._handleKeyDown=function(a){27==a.which&&this._hideTooltip(a)},b.prototype._handleMouseOver=function(a){this._showTooltip(a)},b.prototype._handleMouseOut=function(b){var c=a(b.target);c.is(":focus")||this._hideTooltip(b)},b.prototype._handleBlur=function(a){this._hideTooltip(a)},b});

View file

@ -775,8 +775,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
throttleTimeout = window.setTimeout(handler.bind(this, e), 300); throttleTimeout = window.setTimeout(handler.bind(this, e), 300);
}; };
// Trigger an ajax update after the text field value changes. // Trigger an ajax update after the text field value changes.
inputElement.on("input keypress", throttledHandler); inputElement.on("input", throttledHandler);
var arrowElement = $(document.getElementById(state.downArrowId)); var arrowElement = $(document.getElementById(state.downArrowId));
arrowElement.on("click", handler); arrowElement.on("click", handler);
}); });

133
lib/amd/src/tooltip.js Normal file
View file

@ -0,0 +1,133 @@
define(['jquery'], function($) {
/**
* Tooltip class.
*
* @param {String} selector The css selector for the node(s) to enhance with tooltips.
*/
var Tooltip = function(selector) {
// Tooltip code matches: http://www.w3.org/WAI/PF/aria-practices/#tooltip
this._regionSelector = selector;
// For each node matching the selector - find an aria-describedby attribute pointing to an role="tooltip" element.
$(this._regionSelector).each(function(index, element) {
var tooltipId = $(element).attr('aria-describedby');
if (tooltipId) {
var tooltipele = document.getElementById(tooltipId);
if (tooltipele) {
var correctRole = $(tooltipele).attr('role') == 'tooltip';
if (correctRole) {
$(tooltipele).hide();
// Ensure the trigger for the tooltip is keyboard focusable.
$(element).attr('tabindex', '0');
}
// Attach listeners.
$(element).on('focus', this._handleFocus.bind(this));
$(element).on('mouseover', this._handleMouseOver.bind(this));
$(element).on('mouseout', this._handleMouseOut.bind(this));
$(element).on('blur', this._handleBlur.bind(this));
$(element).on('keydown', this._handleKeyDown.bind(this));
}
}
}.bind(this));
};
/** @type {String} Selector for the page region containing the user navigation. */
Tooltip.prototype._regionSelector = null;
/**
* Find the tooltip referred to by this element and show it.
*
* @param {Event} e
*/
Tooltip.prototype._showTooltip = function(e) {
var triggerElement = $(e.target);
var tooltipId = triggerElement.attr('aria-describedby');
if (tooltipId) {
var tooltipele = $(document.getElementById(tooltipId));
tooltipele.show();
tooltipele.attr('aria-hidden', 'false');
if (!tooltipele.is('.tooltip')) {
// Change the markup to a bootstrap tooltip.
var inner = $('<div class="tooltip-inner"></div>');
inner.append(tooltipele.contents());
tooltipele.append(inner);
tooltipele.addClass('tooltip');
tooltipele.addClass('bottom');
tooltipele.append('<div class="tooltip-arrow"></div>');
}
var pos = triggerElement.offset();
pos.top += triggerElement.height() + 10;
$(tooltipele).offset(pos);
}
};
/**
* Find the tooltip referred to by this element and hide it.
*
* @param {Event} e
*/
Tooltip.prototype._hideTooltip = function(e) {
var triggerElement = $(e.target);
var tooltipId = triggerElement.attr('aria-describedby');
if (tooltipId) {
var tooltipele = document.getElementById(tooltipId);
$(tooltipele).hide();
$(tooltipele).attr('aria-hidden', 'true');
}
};
/**
* Listener for focus events.
* @param {Event} e
*/
Tooltip.prototype._handleFocus = function(e) {
this._showTooltip(e);
};
/**
* Listener for keydown events.
* @param {Event} e
*/
Tooltip.prototype._handleKeyDown = function(e) {
if (e.which == 27) {
this._hideTooltip(e);
}
};
/**
* Listener for mouseover events.
* @param {Event} e
*/
Tooltip.prototype._handleMouseOver = function(e) {
this._showTooltip(e);
};
/**
* Listener for mouseout events.
* @param {Event} e
*/
Tooltip.prototype._handleMouseOut = function(e) {
var triggerElement = $(e.target);
if (!triggerElement.is(":focus")) {
this._hideTooltip(e);
}
};
/**
* Listener for blur events.
* @param {Event} e
*/
Tooltip.prototype._handleBlur = function(e) {
this._hideTooltip(e);
};
return Tooltip;
});

View file

@ -428,6 +428,7 @@ $functions = array(
'classpath' => 'user/externallib.php', 'classpath' => 'user/externallib.php',
'description' => 'Retrieve users information for a specified unique field - If you want to do a user search, use core_user_get_users()', 'description' => 'Retrieve users information for a specified unique field - If you want to do a user search, use core_user_get_users()',
'type' => 'read', 'type' => 'read',
'ajax' => true,
'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update', 'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update',
), ),

View file

@ -53,6 +53,9 @@ class file_storage {
private $dirpermissions; private $dirpermissions;
/** @var int Permissions for new files */ /** @var int Permissions for new files */
private $filepermissions; private $filepermissions;
/** @var array List of formats supported by unoconv */
private $unoconvformats;
/** /**
* Constructor - do not use directly use {@link get_file_storage()} call instead. * Constructor - do not use directly use {@link get_file_storage()} call instead.
@ -206,7 +209,7 @@ class file_storage {
$this->unoconvformats = array_unique($this->unoconvformats); $this->unoconvformats = array_unique($this->unoconvformats);
} }
$sanitized = trim(strtolower($format)); $sanitized = trim(core_text::strtolower($format));
return in_array($sanitized, $this->unoconvformats); return in_array($sanitized, $this->unoconvformats);
} }
@ -226,7 +229,7 @@ class file_storage {
return false; return false;
} }
$fileextension = strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION)); $fileextension = core_text::strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
if (!self::is_format_supported_by_unoconv($fileextension)) { if (!self::is_format_supported_by_unoconv($fileextension)) {
return false; return false;
} }
@ -260,7 +263,6 @@ class file_storage {
$output = null; $output = null;
$currentdir = getcwd(); $currentdir = getcwd();
chdir($tmp); chdir($tmp);
$result = exec('env 1>&2', $output);
$result = exec($cmd, $output); $result = exec($cmd, $output);
chdir($currentdir); chdir($currentdir);
if (!file_exists($newtmpfile)) { if (!file_exists($newtmpfile)) {

View file

@ -278,7 +278,7 @@ abstract class moodleform {
*/ */
function _process_submission($method) { function _process_submission($method) {
$submission = array(); $submission = array();
if (!empty($this->_ajaxformdata) && defined('AJAX_SCRIPT')) { if (!empty($this->_ajaxformdata)) {
$submission = $this->_ajaxformdata; $submission = $this->_ajaxformdata;
} else if ($method == 'post') { } else if ($method == 'post') {
if (!empty($_POST)) { if (!empty($_POST)) {

View file

@ -42,7 +42,10 @@ information provided here is intended especially for developers.
- upgrade_course_modules_sequences() - upgrade_course_modules_sequences()
- upgrade_grade_item_fix_sortorder() - upgrade_grade_item_fix_sortorder()
- upgrade_availability_item() - upgrade_availability_item()
* A new parameter $ajaxformdata was added to the constructor for moodleform. When building a
moodleform in a webservice or ajax script (for example using the new fragments API) we
cannot allow the moodleform to parse it's own data from _GET and _POST - we must pass it as
an array.
* Plugins can extend the navigation for user by declaring the following callback: * Plugins can extend the navigation for user by declaring the following callback:
<frankenstyle>_extend_navigation_user(navigation_node $parentnode, stdClass $user, <frankenstyle>_extend_navigation_user(navigation_node $parentnode, stdClass $user,
context_user $context, stdClass $course, context_user $context, stdClass $course,

View file

@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(b){this._regionSelector=b,this._region=a(b),a(document).on("user-changed",this._showActionsForm.bind(this)),this._region.find('[name="savechanges"]').on("click",this._trigger.bind(this,"save-changes")),this._region.find('[name="resetbutton"]').on("click",this._trigger.bind(this,"reset")),this._region.find("form").on("submit",function(a){a.preventDefault()})};return b.prototype._regionSelector=null,b.prototype._lastUserId=0,b.prototype._region=null,b.prototype._showActionsForm=function(a,b){var c=this._region.find("[data-region=grading-actions-form]");b!=this._lastUserId&&b>0&&(this._lastUserId=b),b>0?c.removeClass("hide"):c.addClass("hide")},b.prototype._trigger=function(b){a(document).trigger(b)},b});

View file

@ -0,0 +1 @@
define(["jquery"],function(a){return{saveFormState:function(b){a(b).trigger("save-form-state");var c=a(b).serialize();a(b).data("saved-form-state",c)},checkFormForChanges:function(b){a(b).trigger("save-form-state");var c=a(b).serialize(),d=a(b).data("saved-form-state");return"undefined"==typeof d?!1:d!=c}}});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/templates"],function(a,b,c,d){var e=function(b){this._regionSelector=b,this._region=a(b),this._userCache=[],a(document).on("user-changed",this._refreshUserInfo.bind(this))};return e.prototype._regionSelector=null,e.prototype._userCache=null,e.prototype._region=null,e.prototype._lastUserId=0,e.prototype._refreshUserInfo=function(e,f){var g=a.Deferred();this._lastUserId!=f&&(this._lastUserId=f,d.render("mod_assign/loading",{}).done(function(e,h){if(this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,e,h),this._region.fadeIn("fast")}.bind(this)),0>f)return void d.render("mod_assign/grading_navigation_no_users",{}).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception);if("undefined"!=typeof this._userCache[f])g.resolve(this._userCache[f]);else{var i=c.call([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[f]}}]);i[0].done(function(b){b.length<1?g.reject("No users"):(a.each(b,function(a,b){this._userCache[b.id]=b}.bind(this)),g.resolve(this._userCache[f]))}.bind(this)).fail(b.exception)}g.done(function(c){var e=a("[data-showuseridentity]").data("showuseridentity").split(","),f=[];c.courseid=a('[data-region="grading-navigation-panel"]').attr("data-courseid"),a.each(e,function(a,b){"undefined"!=typeof c[b]&&""!==c[b]&&(c.hasidentity=!0,f.push(c[b]))}),c.identity=f.join(", "),d.render("mod_assign/grading_navigation_user_summary",c).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception)}.bind(this)).fail(function(){d.render("mod_assign/grading_navigation_no_users",{}).done(function(a,b){this._region.fadeOut("fast",function(){d.replaceNodeContents(this._region,a,b),this._region.fadeIn("fast")}.bind(this))}.bind(this)).fail(b.exception)})}.bind(this)).fail(b.exception))},e});

View file

@ -0,0 +1 @@
define(["jquery","core/notification","core/templates","core/fragment","core/ajax","core/str","mod_assign/grading_form_change_checker"],function(a,b,c,d,e,f,g){var h=function(b){this._regionSelector=b,this._region=a(b),this._userCache=[],a(document).on("user-changed",this._refreshGradingPanel.bind(this)),a(document).on("save-changes",this._submitForm.bind(this)),a(document).on("reset",this._resetForm.bind(this)),a(document).on("save-form-state",this._saveFormState.bind(this))};return h.prototype._regionSelector=null,h.prototype._lastUserId=0,h.prototype._lastAttemptNumber=-1,h.prototype._region=null,h.prototype._niceReplaceNodeContents=function(b,d,e){var f=a.Deferred();return b.fadeOut("fast",function(){c.replaceNodeContents(b,d,e),b.fadeIn("fast",function(){f.resolve()})}),f.promise()},h.prototype._saveFormState=function(){"undefined"!=typeof window.tinyMCE&&window.tinyMCE.triggerSave();var b=a('[data-region="grading-actions-form"] [name="sendstudentnotifications"]').val();a('.gradeform [name="sendstudentnotifications"]').val(b)},h.prototype._submitForm=function(c,d){var f=a(this._region.find("form.gradeform"));a('[data-region="overlay"]').show(),f.trigger("save-form-state");var g=f.serialize(),h=this._region.attr("data-assignmentid");e.call([{methodname:"mod_assign_submit_grading_form",args:{assignmentid:h,userid:this._lastUserId,jsonformdata:JSON.stringify(g)},done:this._handleFormSubmissionResponse.bind(this,g,d),fail:b.exception}])},h.prototype._handleFormSubmissionResponse=function(c,d,e){"undefined"==typeof d&&(d=this._lastUserId),e.length?a(document).trigger("reset",[this._lastUserId,c]):(f.get_strings([{key:"changessaved",component:"core"},{key:"gradechangessaveddetail",component:"mod_assign"}]).done(function(a){b.alert(a[0],a[1])}).fail(b.exception),d==this._lastUserId?a(document).trigger("reset",d):a(document).trigger("user-changed",d)),a('[data-region="overlay"]').hide()},h.prototype._resetForm=function(b,c,d){var e=a.Event("custom");"undefined"==typeof c&&(c=this._lastUserId),this._lastUserId=0,this._refreshGradingPanel(e,c,d)},h.prototype._chooseAttempt=function(c){var d=a(c.target),e=d.data("submissions"),g=a(document.getElementById(e)),h=g.clone(),i=h.wrap(a("<form/>")).html();f.get_strings([{key:"viewadifferentattempt",component:"mod_assign"},{key:"view",component:"core"},{key:"cancel",component:"core"}]).done(function(c){b.confirm(c[0],i,c[1],c[2],function(){var b=a("input:radio[name='select-attemptnumber']:checked").val();this._refreshGradingPanel(null,this._lastUserId,"",b)}.bind(this))}.bind(this)).fail(b.exception)},h.prototype._addPopoutButtons=function(d){var e=a(d);c.render("mod_assign/popout_button",{}).done(function(a){e.find(".fitem_ffilemanager .fitemtitle").append(a),e.find(".fitem_feditor .fitemtitle").append(a),e.find(".fitem_f .fitemtitle").append(a),e.on("click",'[data-region="popout-button"]',this._togglePopout.bind(this))}.bind(this)).fail(b.exception)},h.prototype._togglePopout=function(b){b.preventDefault();var c=a(b.target).closest(".fitem");c.hasClass("popout")?a(".popout").removeClass("popout"):(a(".popout").removeClass("popout"),c.addClass("popout"),c.addClass("moodle-has-zindex"))},h.prototype._refreshGradingPanel=function(e,f,h,i){var j=this._region.attr("data-contextid");"undefined"==typeof h&&(h=""),"undefined"==typeof i&&(i=-1),(this._lastUserId!=f||this._lastAttemptNumber!=i||""!==h)&&(this._lastUserId=f,this._lastAttemptNumber=i,a(document).trigger("start-loading-user"),window.M.util.js_pending("mod-assign-loading-user"),c.render("mod_assign/loading",{}).done(function(c,e){this._niceReplaceNodeContents(this._region,c,e).done(function(){if(f>0){this._region.show();var c={userid:f,attemptnumber:i,jsonformdata:JSON.stringify(h)};d.loadFragment("mod_assign","gradingpanel",j,c).done(function(c,d){this._niceReplaceNodeContents(this._region,c,d).done(function(){g.saveFormState('[data-region="grade-panel"] .gradeform'),a('[data-region="attempt-chooser"]').on("click",this._chooseAttempt.bind(this)),this._addPopoutButtons('[data-region="grade-panel"] .gradeform'),a(document).trigger("finish-loading-user"),window.M.util.js_complete("mod-assign-loading-user")}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)}else{this._region.hide();var e=a('[data-region="review-panel"]');e.length&&this._niceReplaceNodeContents(e,"",""),a(document).trigger("finish-loading-user"),window.M.util.js_complete("mod-assign-loading-user")}}.bind(this))}.bind(this)).fail(b.exception))},h});

View file

@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(){this._region=a('[data-region="review-panel"]')};return b.prototype._region=null,b.prototype.getReviewPanel=function(a){var b=this._region.data("panel-owner");return"undefined"==typeof b&&this._region.data("review-panel-plugin",a),this._region.data("review-panel-plugin")==a?this._region[0]:!1},b});

View file

@ -0,0 +1 @@
define(["core/ajax","jquery","core/templates"],function(a,b,c){return{processResults:function(a,b){var c=[],d=0;for(d=0;d<b.length;d++)c[d]={value:b[d].id,label:b[d].label};return c},transport:function(d,e,f,g){var h=b(d).attr("data-assignmentid"),i=b('[data-region="configure-filters"] input[type="checkbox"]'),j=[];i.each(function(a,c){j[b(c).attr("name")]=b(c).prop("checked")});var k=a.call([{methodname:"mod_assign_list_participants",args:{assignid:h,groupid:0,filter:e,limit:30}}]);k[0].then(function(a){var d=[],e=b("[data-showuseridentity]").data("showuseridentity").split(",");b.each(a,function(a,f){var g=f,h=[],i=!0;j.filter_submitted&&!f.submitted&&(i=!1),j.filter_notsubmitted&&f.submitted&&(i=!1),j.filter_requiregrading&&!f.requiregrading&&(i=!1),i&&(b.each(e,function(a,b){"undefined"!=typeof f[b]&&""!==f[b]&&(g.hasidentity=!0,h.push(f[b]))}),g.identity=h.join(", "),d.push(c.render("mod_assign/list_participant_user_summary",g)))}),b.when.apply(b.when,d).then(function(){var c=arguments,d=0;b.each(a,function(a,b){b.label=c[d],d++}),f(a)})},g)}}});

View file

@ -0,0 +1,89 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript controller for the "Actions" panel at the bottom of the page.
*
* @module mod_assign/grading_actions
* @package mod_assign
* @class GradingActions
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery'], function($) {
/**
* GradingActions class.
*
* @class GradingActions
* @param {String} selector The selector for the page region containing the actions panel.
*/
var GradingActions = function(selector) {
this._regionSelector = selector;
this._region = $(selector);
$(document).on('user-changed', this._showActionsForm.bind(this));
this._region.find('[name="savechanges"]').on('click', this._trigger.bind(this, 'save-changes'));
this._region.find('[name="resetbutton"]').on('click', this._trigger.bind(this, 'reset'));
this._region.find('form').on('submit', function(e) { e.preventDefault(); });
};
/** @type {String} Selector for the page region containing the user navigation. */
GradingActions.prototype._regionSelector = null;
/** @type {Integer} Remember the last user id to prevent unnessecary reloads. */
GradingActions.prototype._lastUserId = 0;
/** @type {JQuery} JQuery node for the page region containing the user navigation. */
GradingActions.prototype._region = null;
/**
* Show the actions if there is valid user.
*
* @method _showActionsForm
* @private
* @param {Event} event
* @param {Integer} userid
* @return {Deferred} promise resolved when the animations are complete.
*/
GradingActions.prototype._showActionsForm = function(event, userid) {
var form = this._region.find('[data-region=grading-actions-form]');
if (userid != this._lastUserId && userid > 0) {
this._lastUserId = userid;
}
if (userid > 0) {
form.removeClass('hide');
} else {
form.addClass('hide');
}
};
/**
* Trigger the named action.
*
* @method _trigger
* @private
* @param {String} action
*/
GradingActions.prototype._trigger = function(action) {
$(document).trigger(action);
};
return GradingActions;
});

View file

@ -0,0 +1,60 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Simple method to check for changes to a form between two points in time.
*
* @module mod_assign/grading_form_change_checker
* @package mod_assign
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery'], function($) {
return /** @alias module:mod_assign/grading_form_change_checker */ {
/**
* Save the values in the form to a data attribute so they can be compared later for changes.
*
* @method saveFormState
* @param {String} selector The selector for the form element.
*/
saveFormState: function(selector) {
$(selector).trigger('save-form-state');
var data = $(selector).serialize();
$(selector).data('saved-form-state', data);
},
/**
* Compare the current values in the form to the previously saved state.
*
* @method checkFormForChanges
* @param {String} selector The selector for the form element.
* @return {Boolean} True if there are changes to the form data.
*/
checkFormForChanges: function(selector) {
$(selector).trigger('save-form-state');
var data = $(selector).serialize(),
previousdata = $(selector).data('saved-form-state');
if (typeof previousdata === 'undefined') {
return false;
}
return (previousdata != data);
}
};
});

View file

@ -0,0 +1,470 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript to handle changing users via the user selector in the header.
*
* @module mod_assign/grading_navigation
* @package mod_assign
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',
'core/ajax', 'mod_assign/grading_form_change_checker'],
function($, notification, str, autocomplete, ajax, checker) {
/**
* GradingNavigation class.
*
* @class GradingNavigation
* @param {String} selector The selector for the page region containing the user navigation.
*/
var GradingNavigation = function(selector) {
this._regionSelector = selector;
this._region = $(selector);
this._filters = [];
this._users = [];
this._filteredUsers = [];
// Get the current user list from a webservice.
this._loadAllUsers();
// Attach listeners to the select and arrow buttons.
this._region.find('[data-action="previous-user"]').on('click', this._handlePreviousUser.bind(this));
this._region.find('[data-action="next-user"]').on('click', this._handleNextUser.bind(this));
this._region.find('[data-action="change-user"]').on('change', this._handleChangeUser.bind(this));
this._region.find('[data-region="user-filters"]').on('click', this._toggleExpandFilters.bind(this));
$(document).on('user-changed', this._refreshSelector.bind(this));
// Position the configure filters panel under the link that expands it.
var toggleLink = this._region.find('[data-region="user-filters"]');
var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));
configPanel.on('change', '[type="checkbox"]', this._filterChanged.bind(this));
var userid = $('[data-region="grading-navigation-panel"]').data('first-userid');
if (userid) {
this._selectUserById(userid);
}
str.get_string('changeuser', 'mod_assign').done(function(s) {
autocomplete.enhance('[data-action=change-user]', false, 'mod_assign/participant_selector', s);
}.bind(this)
).fail(notification.exception);
// We do not allow navigation while ajax requests are pending.
$(document).bind("start-loading-user", function(){
this._isLoading = true;
}.bind(this));
$(document).bind("finish-loading-user", function(){
this._isLoading = false;
}.bind(this));
};
/** @type {Boolean} Boolean tracking active ajax requests. */
GradingNavigation.prototype._isLoading = false;
/** @type {String} Selector for the page region containing the user navigation. */
GradingNavigation.prototype._regionSelector = null;
/** @type {Array} The list of active filter keys */
GradingNavigation.prototype._filters = null;
/** @type {Array} The list of users */
GradingNavigation.prototype._users = null;
/** @type {JQuery} JQuery node for the page region containing the user navigation. */
GradingNavigation.prototype._region = null;
/**
* Load the list of all users for this assignment.
*
* @private
* @method _loadAllUsers
*/
GradingNavigation.prototype._loadAllUsers = function() {
var select = this._region.find('[data-action=change-user]');
var assignmentid = select.attr('data-assignmentid');
var groupid = select.attr('data-groupid');
ajax.call([{
methodname: 'mod_assign_list_participants',
args: { assignid: assignmentid, groupid: groupid, filter: '', onlyids: true },
done: this._usersLoaded.bind(this),
fail: notification.exception
}]);
};
/**
* Call back to rebuild the user selector and x of y info when the user list is updated.
*
* @private
* @method _usersLoaded
* @param {Array} users
*/
GradingNavigation.prototype._usersLoaded = function(users) {
this._filteredUsers = this._users = users;
if (this._users.length) {
// Position the configure filters panel under the link that expands it.
var toggleLink = this._region.find('[data-region="user-filters"]');
var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));
configPanel.find('[type="checkbox"]').trigger('change');
} else {
this._selectNoUser();
}
};
/**
* Close the configure filters panel if a click is detected outside of it.
*
* @private
* @method _checkClickOutsideConfigureFilters
* @param {Event}
*/
GradingNavigation.prototype._checkClickOutsideConfigureFilters = function(event) {
var configPanel = this._region.find('[data-region="configure-filters"]');
if (!configPanel.is(event.target) && configPanel.has(event.target).length === 0) {
var toggleLink = this._region.find('[data-region="user-filters"]');
configPanel.hide();
configPanel.attr('aria-hidden', 'true');
toggleLink.attr('aria-expanded', 'false');
$(document).unbind('click.mod_assign_grading_navigation');
}
};
/**
* Turn a filter on or off.
*
* @private
* @method _filterChanged
* @param {Event}
*/
GradingNavigation.prototype._filterChanged = function(event) {
var name = $(event.target).attr('name');
var key = name.split('_').pop();
var enabled = $(event.target).prop('checked');
if (enabled) {
if (this._filters.indexOf(key) == -1) {
this._filters[this._filters.length] = key;
}
} else {
var index = this._filters.indexOf(key);
if (index != -1) {
this._filters.splice(index, 1);
}
}
// Update the active filter string.
var filterlist = [];
this._region.find('[data-region="configure-filters"]').find('[type="checkbox"]').each(function(idx, ele) {
if ($(ele).prop('checked')) {
filterlist[filterlist.length] = $(ele).closest('label').text();
}
}.bind(this));
if (filterlist.length) {
this._region.find('[data-region="user-filters"] span').text(filterlist.join(', '));
} else {
str.get_string('nofilters', 'mod_assign').done(function(s) {
this._region.find('[data-region="user-filters"] span').text(s);
}.bind(this)).fail(notification.exception);
}
// Filter the options in the select box that do not match the current filters.
var select = this._region.find('[data-action=change-user]');
var userid = select.attr('data-selected');
var foundIndex = 0;
this._filteredUsers = [];
$.each(this._users, function(index, user) {
var show = true;
$.each(this._filters, function(filterindex, filter) {
if (filter == "submitted") {
if (user.submitted == "0") {
show = false;
}
} else if (filter == "notsubmitted") {
if (user.submitted == "1") {
show = false;
}
} else if (filter == "requiregrading") {
if (user.requiregrading == "0") {
show = false;
}
}
}.bind(this));
if (show) {
this._filteredUsers[this._filteredUsers.length] = user;
if (userid == user.id) {
foundIndex = index;
}
}
}.bind(this));
if (this._filteredUsers.length) {
this._selectUserById(this._filteredUsers[foundIndex].id);
} else {
this._selectNoUser();
}
};
/**
* Select no users, because no users match the filters.
*
* @private
* @method _selectNoUser
*/
GradingNavigation.prototype._selectNoUser = function() {
// Detect unsaved changes, and offer to save them - otherwise change user right now.
if (this._isLoading) {
return;
}
if (checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')) {
// Form has changes, so we need to confirm before switching users.
str.get_strings([
{ key: 'unsavedchanges', component: 'mod_assign' },
{ key: 'unsavedchangesquestion', component: 'mod_assign' },
{ key: 'saveandcontinue', component: 'mod_assign' },
{ key: 'cancel', component: 'core' },
]).done(function(strs) {
notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
$(document).trigger('save-changes', -1);
});
}.bind(this));
} else {
$(document).trigger('user-changed', -1);
}
};
/**
* Select the specified user by id.
*
* @private
* @method _selectUserById
* @param {Number} userid
*/
GradingNavigation.prototype._selectUserById = function(userid) {
var select = this._region.find('[data-action=change-user]');
var useridnumber = parseInt(userid, 10);
// Detect unsaved changes, and offer to save them - otherwise change user right now.
if (this._isLoading) {
return;
}
if (checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')) {
// Form has changes, so we need to confirm before switching users.
str.get_strings([
{ key: 'unsavedchanges', component: 'mod_assign' },
{ key: 'unsavedchangesquestion', component: 'mod_assign' },
{ key: 'saveandcontinue', component: 'mod_assign' },
{ key: 'cancel', component: 'core' },
]).done(function(strs) {
notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
$(document).trigger('save-changes', useridnumber);
});
}.bind(this));
} else {
select.attr('data-selected', userid);
if (!isNaN(useridnumber) && useridnumber > 0) {
$(document).trigger('user-changed', userid);
}
}
};
/**
* Expand or collapse the filter config panel.
*
* @private
* @method _toggleExpandFilters
* @param {Event}
*/
GradingNavigation.prototype._toggleExpandFilters = function(event) {
event.preventDefault();
var toggleLink = $(event.target).closest('[data-region="user-filters"]');
var expanded = toggleLink.attr('aria-expanded') == 'true';
var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));
if (expanded) {
configPanel.hide();
configPanel.attr('aria-hidden', 'true');
toggleLink.attr('aria-expanded', 'false');
$(document).unbind('click.mod_assign_grading_navigation');
} else {
configPanel.css('display', 'inline-block');
configPanel.attr('aria-hidden', 'false');
toggleLink.attr('aria-expanded', 'true');
event.stopPropagation();
$(document).on('click.mod_assign_grading_navigation', this._checkClickOutsideConfigureFilters.bind(this));
}
};
/**
* Change to the previous user in the grading list.
*
* @private
* @method _handlePreviousUser
* @param {Event} e
*/
GradingNavigation.prototype._handlePreviousUser = function(e) {
e.preventDefault();
var select = this._region.find('[data-action=change-user]');
var currentUserId = select.attr('data-selected');
var i = 0, currentIndex = 0;
for (i = 0; i < this._filteredUsers.length; i++) {
if (this._filteredUsers[i].id == currentUserId) {
currentIndex = i;
break;
}
}
var count = this._filteredUsers.length;
var newIndex = (currentIndex - 1);
if (newIndex < 0) {
newIndex = count - 1;
}
if (count) {
this._selectUserById(this._filteredUsers[newIndex].id);
}
};
/**
* Change to the next user in the grading list.
*
* @param {Event} e
*/
GradingNavigation.prototype._handleNextUser = function(e) {
e.preventDefault();
var select = this._region.find('[data-action=change-user]');
var currentUserId = select.attr('data-selected');
var i = 0, currentIndex = 0;
for (i = 0; i < this._filteredUsers.length; i++) {
if (this._filteredUsers[i].id == currentUserId) {
currentIndex = i;
break;
}
}
var count = this._filteredUsers.length;
var newIndex = (currentIndex + 1) % count;
if (count) {
this._selectUserById(this._filteredUsers[newIndex].id);
}
};
/**
* Rebuild the x of y string.
*
* @private
* @method _refreshCount
*/
GradingNavigation.prototype._refreshCount = function() {
var select = this._region.find('[data-action=change-user]');
var userid = select.attr('data-selected');
var i = 0;
var currentIndex = 0;
if (isNaN(userid) || userid <= 0) {
this._region.find('[data-region="user-count"]').hide();
} else {
this._region.find('[data-region="user-count"]').show();
for (i = 0; i < this._filteredUsers.length; i++) {
if (this._filteredUsers[i].id == userid) {
currentIndex = i;
break;
}
}
var count = this._filteredUsers.length;
if (count) {
currentIndex += 1;
}
var param = { x: currentIndex, y: count };
str.get_string('xofy', 'mod_assign', param).done(function(s) {
this._region.find('[data-region="user-count-summary"]').text(s);
}.bind(this)).fail(notification.exception);
}
};
/**
* Respond to a user-changed event by updating the selector.
*
* @private
* @method _refreshSelector
* @param {Event} event
* @param {String} userid
*/
GradingNavigation.prototype._refreshSelector = function(event, userid) {
var select = this._region.find('[data-action=change-user]');
userid = parseInt(userid, 10);
if (!isNaN(userid) && userid > 0) {
select.attr('data-selected', userid);
}
this._refreshCount();
};
/**
* Change to a different user in the grading list.
*
* @private
* @method _handleChangeUser
* @param {Event}
*/
GradingNavigation.prototype._handleChangeUser = function() {
var select = this._region.find('[data-action=change-user]');
var userid = parseInt(select.val(), 10);
if (this._isLoading) {
return;
}
if (checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')) {
// Form has changes, so we need to confirm before switching users.
str.get_strings([
{ key: 'unsavedchanges', component: 'mod_assign' },
{ key: 'unsavedchangesquestion', component: 'mod_assign' },
{ key: 'saveandcontinue', component: 'mod_assign' },
{ key: 'cancel', component: 'core' },
]).done(function(strs) {
notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
$(document).trigger('save-changes', userid);
});
}.bind(this));
} else {
if (!isNaN(userid) && userid > 0) {
select.attr('data-selected', userid);
$(document).trigger('user-changed', userid);
}
}
};
return GradingNavigation;
});

View file

@ -0,0 +1,147 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript controller for the "User summary" panel at the top of the page.
*
* @module mod_assign/grading_navigation_user_info
* @package mod_assign
* @class UserInfo
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function($, notification, ajax, templates) {
/**
* UserInfo class.
*
* @class UserInfo
* @param {String} selector The selector for the page region containing the user navigation.
*/
var UserInfo = function(selector) {
this._regionSelector = selector;
this._region = $(selector);
this._userCache = [];
$(document).on('user-changed', this._refreshUserInfo.bind(this));
};
/** @type {String} Selector for the page region containing the user navigation. */
UserInfo.prototype._regionSelector = null;
/** @type {Array} Cache of user info contexts. */
UserInfo.prototype._userCache = null;
/** @type {JQuery} JQuery node for the page region containing the user navigation. */
UserInfo.prototype._region = null;
/** @type {Integer} Remember the last user id to prevent unnessecary reloads. */
UserInfo.prototype._lastUserId = 0;
/**
* Get the user context - re-render the template in the page.
*
* @private
* @method _refreshUserInfo
* @param {Event} event
* @param {Number} userid
*/
UserInfo.prototype._refreshUserInfo = function(event, userid) {
var promise = $.Deferred();
// Skip reloading if it is the same user.
if (this._lastUserId == userid) {
return;
}
this._lastUserId = userid;
// First insert the loading template.
templates.render('mod_assign/loading', {}).done(function(html, js) {
// Update the page.
this._region.fadeOut("fast", function() {
templates.replaceNodeContents(this._region, html, js);
this._region.fadeIn("fast");
}.bind(this));
if (userid < 0) {
// Render the template.
templates.render('mod_assign/grading_navigation_no_users', {}).done(function(html, js) {
// Update the page.
this._region.fadeOut("fast", function() {
templates.replaceNodeContents(this._region, html, js);
this._region.fadeIn("fast");
}.bind(this));
}.bind(this)).fail(notification.exception);
return;
}
if (typeof this._userCache[userid] !== "undefined") {
promise.resolve(this._userCache[userid]);
} else {
// Load context from ajax.
var requests = ajax.call([{
methodname: 'core_user_get_users_by_field',
args: { field: 'id', values: [ userid ] }
}]);
requests[0].done(function(result) {
if (result.length < 1) {
promise.reject('No users');
} else {
$.each(result, function(index, user) {
this._userCache[user.id] = user;
}.bind(this));
promise.resolve(this._userCache[userid]);
}
}.bind(this)).fail(notification.exception);
}
promise.done(function(context) {
var identityfields = $('[data-showuseridentity]').data('showuseridentity').split(','),
identity = [];
// Render the template.
context.courseid = $('[data-region="grading-navigation-panel"]').attr('data-courseid');
// Build a string for the visible identity fields listed in showuseridentity config setting.
$.each(identityfields, function(i, k) {
if (typeof context[k] !== 'undefined' && context[k] !== '') {
context.hasidentity = true;
identity.push(context[k]);
}
});
context.identity = identity.join(', ');
templates.render('mod_assign/grading_navigation_user_summary', context).done(function(html, js) {
// Update the page.
this._region.fadeOut("fast", function() {
templates.replaceNodeContents(this._region, html, js);
this._region.fadeIn("fast");
}.bind(this));
}.bind(this)).fail(notification.exception);
}.bind(this)).fail(function() {
// Render the template.
templates.render('mod_assign/grading_navigation_no_users', {}).done(function(html, js) {
// Update the page.
this._region.fadeOut("fast", function() {
templates.replaceNodeContents(this._region, html, js);
this._region.fadeIn("fast");
}.bind(this));
}.bind(this)).fail(notification.exception);
});
}.bind(this)).fail(notification.exception);
};
return UserInfo;
});

View file

@ -0,0 +1,307 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript controller for the "Grading" panel at the right of the page.
*
* @module mod_assign/grading_panel
* @package mod_assign
* @class GradingPanel
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/notification', 'core/templates', 'core/fragment',
'core/ajax', 'core/str', 'mod_assign/grading_form_change_checker'],
function($, notification, templates, fragment, ajax, str, checker) {
/**
* GradingPanel class.
*
* @class GradingPanel
* @param {String} selector The selector for the page region containing the user navigation.
*/
var GradingPanel = function(selector) {
this._regionSelector = selector;
this._region = $(selector);
this._userCache = [];
$(document).on('user-changed', this._refreshGradingPanel.bind(this));
$(document).on('save-changes', this._submitForm.bind(this));
$(document).on('reset', this._resetForm.bind(this));
$(document).on('save-form-state', this._saveFormState.bind(this));
};
/** @type {String} Selector for the page region containing the user navigation. */
GradingPanel.prototype._regionSelector = null;
/** @type {Integer} Remember the last user id to prevent unnessecary reloads. */
GradingPanel.prototype._lastUserId = 0;
/** @type {Integer} Remember the last attempt number to prevent unnessecary reloads. */
GradingPanel.prototype._lastAttemptNumber = -1;
/** @type {JQuery} JQuery node for the page region containing the user navigation. */
GradingPanel.prototype._region = null;
/**
* Fade the dom node out, update it, and fade it back.
*
* @private
* @method _niceReplaceNodeContents
* @param {JQuery} node
* @param {String} html
* @param {String} js
* @return {Deferred} promise resolved when the animations are complete.
*/
GradingPanel.prototype._niceReplaceNodeContents = function(node, html, js) {
var promise = $.Deferred();
node.fadeOut("fast", function() {
templates.replaceNodeContents(node, html, js);
node.fadeIn("fast", function() {
promise.resolve();
});
});
return promise.promise();
};
/**
* Make sure all form fields have the latest saved state.
* @private
* @method _saveFormState
*/
GradingPanel.prototype._saveFormState = function() {
// Grrrrr! TinyMCE you know what you did.
if (typeof window.tinyMCE !== 'undefined') {
window.tinyMCE.triggerSave();
}
// Copy data from notify students checkbox which was moved out of the form.
var checked = $('[data-region="grading-actions-form"] [name="sendstudentnotifications"]').val();
$('.gradeform [name="sendstudentnotifications"]').val(checked);
};
/**
* Make form submit via ajax.
*
* @private
* @method _submitForm
*/
GradingPanel.prototype._submitForm = function(event, nextUserId) {
// The form was submitted - send it via ajax instead.
var form = $(this._region.find('form.gradeform'));
$('[data-region="overlay"]').show();
// We call this, so other modules can update the form with the latest state.
form.trigger('save-form-state');
// Now we get all the current values from the form.
var data = form.serialize();
var assignmentid = this._region.attr('data-assignmentid');
// Now we can continue...
ajax.call([{
methodname: 'mod_assign_submit_grading_form',
args: {assignmentid: assignmentid, userid: this._lastUserId, jsonformdata: JSON.stringify(data)},
done: this._handleFormSubmissionResponse.bind(this, data, nextUserId),
fail: notification.exception
}]);
};
/**
* Handle form submission response.
*
* @private
* @method _handleFormSubmissionResponse
* @param {Array} formdata - submitted values
* @param {Integer} nextUserId - optional. The id of the user to load after the form is saved.
* @param {Array} response List of errors.
*/
GradingPanel.prototype._handleFormSubmissionResponse = function(formdata, nextUserId, response) {
if (typeof nextUserId === "undefined") {
nextUserId = this._lastUserId;
}
if (response.length) {
// There was an error saving the grade. Re-render the form using the submitted data so we can show
// validation errors.
$(document).trigger('reset', [this._lastUserId, formdata]);
} else {
str.get_strings([
{ key: 'changessaved', component: 'core' },
{ key: 'gradechangessaveddetail', component: 'mod_assign' },
]).done(function(strs) {
notification.alert(strs[0], strs[1]);
}).fail(notification.exception);
if (nextUserId == this._lastUserId) {
$(document).trigger('reset', nextUserId);
} else {
$(document).trigger('user-changed', nextUserId);
}
}
$('[data-region="overlay"]').hide();
};
/**
* Refresh form with default values.
*
* @private
* @method _resetForm
* @param {Event} e
* @param {Integer} userid
* @param {Array} formdata
*/
GradingPanel.prototype._resetForm = function(e, userid, formdata) {
// The form was cancelled - refresh with default values.
var event = $.Event("custom");
if (typeof userid == "undefined") {
userid = this._lastUserId;
}
this._lastUserId = 0;
this._refreshGradingPanel(event, userid, formdata);
};
/**
* Open a picker to choose an older attempt.
*
* @private
* @method _chooseAttempt
*/
GradingPanel.prototype._chooseAttempt = function(e) {
// Show a dialog.
// The form is in the element pointed to by data-submissions.
var link = $(e.target);
var submissionsId = link.data('submissions');
var submissionsform = $(document.getElementById(submissionsId));
var formcopy = submissionsform.clone();
var formhtml = formcopy.wrap($('<form/>')).html();
str.get_strings([
{ key: 'viewadifferentattempt', component: 'mod_assign' },
{ key: 'view', component: 'core' },
{ key: 'cancel', component: 'core' },
]).done(function(strs) {
notification.confirm(strs[0], formhtml, strs[1], strs[2], function() {
var attemptnumber = $("input:radio[name='select-attemptnumber']:checked").val();
this._refreshGradingPanel(null, this._lastUserId, '', attemptnumber);
}.bind(this));
}.bind(this)).fail(notification.exception);
};
/**
* Add popout buttons
*
* @private
* @method _addPopoutButtons
* @param {JQuery} region The region to add popout buttons to.
*/
GradingPanel.prototype._addPopoutButtons = function(selector) {
var region = $(selector);
templates.render('mod_assign/popout_button', {}).done(function(html) {
region.find('.fitem_ffilemanager .fitemtitle').append(html);
region.find('.fitem_feditor .fitemtitle').append(html);
region.find('.fitem_f .fitemtitle').append(html);
region.on('click', '[data-region="popout-button"]', this._togglePopout.bind(this));
}.bind(this)).fail(notification.exception);
};
/**
* Make a div "popout" or "popback".
*
* @private
* @method _togglePopout
* @param {Event} event
*/
GradingPanel.prototype._togglePopout = function(event) {
event.preventDefault();
var container = $(event.target).closest('.fitem');
if (container.hasClass('popout')) {
$('.popout').removeClass('popout');
} else {
$('.popout').removeClass('popout');
container.addClass('popout');
container.addClass('moodle-has-zindex');
}
};
/**
* Get the user context - re-render the template in the page.
*
* @private
* @method _refreshGradingPanel
* @param {Event} event
* @param {Number} userid
* @param {String} serialised submission data.
*/
GradingPanel.prototype._refreshGradingPanel = function(event, userid, submissiondata, attemptnumber) {
var contextid = this._region.attr('data-contextid');
if (typeof submissiondata === 'undefined') {
submissiondata = '';
}
if (typeof attemptnumber === 'undefined') {
attemptnumber = -1;
}
// Skip reloading if it is the same user.
if (this._lastUserId == userid && this._lastAttemptNumber == attemptnumber && submissiondata === '') {
return;
}
this._lastUserId = userid;
this._lastAttemptNumber = attemptnumber;
$(document).trigger('start-loading-user');
// Tell behat to back off too.
window.M.util.js_pending('mod-assign-loading-user');
// First insert the loading template.
templates.render('mod_assign/loading', {}).done(function(html, js) {
// Update the page.
this._niceReplaceNodeContents(this._region, html, js).done(function() {
if (userid > 0) {
this._region.show();
// Reload the grading form "fragment" for this user.
var params = { userid: userid, attemptnumber: attemptnumber, jsonformdata: JSON.stringify(submissiondata) };
fragment.loadFragment('mod_assign', 'gradingpanel', contextid, params).done(function(html, js) {
this._niceReplaceNodeContents(this._region, html, js)
.done(function() {
checker.saveFormState('[data-region="grade-panel"] .gradeform');
$('[data-region="attempt-chooser"]').on('click', this._chooseAttempt.bind(this));
this._addPopoutButtons('[data-region="grade-panel"] .gradeform');
$(document).trigger('finish-loading-user');
// Tell behat we are friends again.
window.M.util.js_complete('mod-assign-loading-user');
}.bind(this))
.fail(notification.exception);
}.bind(this)).fail(notification.exception);
} else {
this._region.hide();
var reviewPanel = $('[data-region="review-panel"]');
if (reviewPanel.length) {
this._niceReplaceNodeContents(reviewPanel, '', '');
}
$(document).trigger('finish-loading-user');
// Tell behat we are friends again.
window.M.util.js_complete('mod-assign-loading-user');
}
}.bind(this));
}.bind(this)).fail(notification.exception);
};
return GradingPanel;
});

View file

@ -0,0 +1,62 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript controller for the "Review" panel at the left of the page.
*
* @module mod_assign/grading_review_panel
* @package mod_assign
* @class GradingReviewPanel
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery'], function($) {
/**
* GradingReviewPanel class.
*
* @class GradingReviewPanel
* @param {String} selector The selector for the page region containing the user navigation.
*/
var GradingReviewPanel = function() {
this._region = $('[data-region="review-panel"]');
};
/** @type {JQuery} JQuery node for the page region containing the user navigation. */
GradingReviewPanel.prototype._region = null;
/**
* It is first come first served to get ownership of the grading review panel.
* There can be only one.
*
* @public
* @method getReviewPanel
* @param {String} pluginname - the first plugin to ask for the panel gets it.
* @return {DOMNode} or false
*/
GradingReviewPanel.prototype.getReviewPanel = function(pluginname) {
var owner = this._region.data('panel-owner');
if (typeof owner == "undefined") {
this._region.data('review-panel-plugin', pluginname);
}
if (this._region.data('review-panel-plugin') == pluginname) {
return this._region[0];
}
return false;
};
return GradingReviewPanel;
});

View file

@ -0,0 +1,113 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Custom auto-complete adapter to load users from the assignment list_participants webservice.
*
* @module mod_assign/participants_selector
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax', 'jquery', 'core/templates'], function(ajax, $, templates) {
return /** @alias module:mod_assign/participants_selector */ {
// Public variables and functions.
/**
* Process the results returned from transport (convert to value + label)
*
* @method processResults
* @param {String} selector
* @param {Array} data
* @return {Array}
*/
processResults: function(selector, data) {
var results = [], i = 0;
for (i = 0; i < data.length; i++) {
results[i] = { value: data[i].id, label: data[i].label };
}
return results;
},
/**
* Fetch results based on the current query. This also renders each result from a template before returning them.
*
* @method transport
* @param {String} selector Selector for the original select element
* @param {String} query Current search string
* @param {Function} success Success handler
* @param {Function} failure Failure handler
*/
transport: function(selector, query, success, failure) {
var assignmentid = $(selector).attr('data-assignmentid');
var filters = $('[data-region="configure-filters"] input[type="checkbox"]');
var filterstrings = [];
filters.each(function(index, element) {
filterstrings[$(element).attr('name')] = $(element).prop('checked');
});
var promise = ajax.call([{
methodname: 'mod_assign_list_participants', args: { assignid: assignmentid, groupid: 0, filter: query, limit: 30 }
}]);
promise[0].then(function(results) {
var promises = [];
var identityfields = $('[data-showuseridentity]').data('showuseridentity').split(',');
// We got the results, now we loop over them and render each one from a template.
$.each(results, function(index, user) {
var ctx = user,
identity = [],
show = true;
if (filterstrings.filter_submitted && !user.submitted) {
show = false;
}
if (filterstrings.filter_notsubmitted && user.submitted) {
show = false;
}
if (filterstrings.filter_requiregrading && !user.requiregrading) {
show = false;
}
if (show) {
$.each(identityfields, function(i, k) {
if (typeof user[k] !== 'undefined' && user[k] !== '') {
ctx.hasidentity = true;
identity.push(user[k]);
}
});
ctx.identity = identity.join(', ');
promises.push(templates.render('mod_assign/list_participant_user_summary', ctx));
}
});
// When all the templates have been rendered, call the success handler.
$.when.apply($.when, promises).then(function() {
var args = arguments,
i = 0;
$.each(results, function(index, user) {
user.label = args[i];
i++;
});
success(results);
});
}, failure);
}
};
});

View file

@ -0,0 +1,163 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Renderable that initialises the grading "app".
*
* @package mod_assign
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_assign\output;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
use renderable;
use templatable;
use stdClass;
/**
* Grading app renderable.
*
* @package mod_assign
* @since Moodle 3.1
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grading_app implements templatable, renderable {
/**
* The initial user id.
*/
public $userid = 0;
/**
* The initial group id.
*/
public $groupid = 0;
/**
* The assignment instance.
*/
public $assignment = null;
/**
* Constructor for this renderable.
*
* @param int $userid The user we will open the grading app too.
* @param int $groupid If groups are enabled this is the current course group.
* @param assign $assignment The assignment class
*/
public function __construct($userid, $groupid, $assignment) {
$this->userid = $userid;
$this->groupid = $groupid;
$this->assignment = $assignment;
$this->participants = $assignment->list_participants_with_filter_status_and_group($groupid);
if (!$this->userid && count($this->participants)) {
$this->userid = reset($this->participants)->id;
}
}
/**
* Export this class data as a flat list for rendering in a template.
*
* @param renderer_base $output The current page renderer.
* @return stdClass - Flat list of exported data.
*/
public function export_for_template(renderer_base $output) {
global $CFG;
$export = new stdClass();
$export->userid = $this->userid;
$export->assignmentid = $this->assignment->get_instance()->id;
$export->cmid = $this->assignment->get_course_module()->id;
$export->contextid = $this->assignment->get_context()->id;
$export->groupid = $this->groupid;
$export->name = $this->assignment->get_instance()->name;
$export->courseid = $this->assignment->get_course()->id;
$export->participants = array();
$num = 1;
foreach ($this->participants as $idx => $record) {
$user = new stdClass();
$user->id = $record->id;
$user->fullname = fullname($record);
$user->requiregrading = $record->requiregrading;
$user->submitted = $record->submitted;
if (!empty($record->groupid)) {
$user->groupid = $record->groupid;
$user->groupname = $record->groupname;
}
if ($record->id == $this->userid) {
$export->index = $num;
$user->current = true;
}
$export->participants[] = $user;
$num++;
}
$feedbackplugins = $this->assignment->get_feedback_plugins();
$showreview = false;
foreach ($feedbackplugins as $plugin) {
if ($plugin->is_enabled() && $plugin->is_visible()) {
if ($plugin->supports_review_panel()) {
$showreview = true;
}
}
}
$export->showreview = $showreview;
$time = time();
$export->count = count($export->participants);
$export->coursename = $this->assignment->get_course_context()->get_context_name();
$export->caneditsettings = has_capability('mod/assign:addinstance', $this->assignment->get_context());
$export->duedate = $this->assignment->get_instance()->duedate;
$export->duedatestr = userdate($this->assignment->get_instance()->duedate);
// Time remaining.
$due = '';
if ($export->duedate - $time <= 0) {
$due = get_string('assignmentisdue', 'assign');
} else {
$due = get_string('timeremainingcolon', 'assign', format_time($export->duedate - $time));
}
$export->timeremainingstr = $due;
if ($export->duedate < $time) {
$export->cutoffdate = $this->assignment->get_instance()->cutoffdate;
$cutoffdate = $export->cutoffdate;
if ($cutoffdate) {
if ($cutoffdate > $time) {
$late = get_string('latesubmissionsaccepted', 'assign', userdate($export->cutoffdate));
} else {
$late = get_string('nomoresubmissionsaccepted', 'assign');
}
$export->cutoffdatestr = $late;
}
}
$export->defaultsendnotifications = $this->assignment->get_instance()->sendstudentnotifications;
$export->rarrow = $output->rarrow();
$export->larrow = $output->larrow();
// List of identity fields to display (the user info will not contain any fields the user cannot view anyway).
$export->showuseridentity = $CFG->showuseridentity;
return $export;
}
}

View file

@ -190,4 +190,24 @@ $functions = array(
'capabilities' => 'mod/assign:view', 'capabilities' => 'mod/assign:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
), ),
'mod_assign_list_participants' => array(
'classname' => 'mod_assign_external',
'methodname' => 'list_participants',
'classpath' => 'mod/assign/externallib.php',
'description' => 'List the participants for a single assignment, with some summary info about their submissions.',
'type' => 'read',
'ajax' => true,
'capabilities' => 'mod/assign:view, mod/assign:viewgrades'
),
'mod_assign_submit_grading_form' => array(
'classname' => 'mod_assign_external',
'methodname' => 'submit_grading_form',
'classpath' => 'mod/assign/externallib.php',
'description' => 'Submit the grading form data via ajax',
'type' => 'write',
'ajax' => true,
'capabilities' => 'mod/assign:grade'
),
); );

View file

@ -2266,6 +2266,7 @@ class mod_assign_external extends external_api {
) )
); );
} }
/** /**
* Describes the parameters for view_submission_status. * Describes the parameters for view_submission_status.
* *
@ -2567,4 +2568,194 @@ class mod_assign_external extends external_api {
); );
} }
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function list_participants_parameters() {
return new external_function_parameters(
array(
'assignid' => new external_value(PARAM_INT, 'assign instance id'),
'groupid' => new external_value(PARAM_INT, 'group id'),
'filter' => new external_value(PARAM_RAW, 'search string to filter the results'),
'skip' => new external_value(PARAM_INT, 'number of records to skip', VALUE_DEFAULT, 0),
'limit' => new external_value(PARAM_INT, 'maximum number of records to return', VALUE_DEFAULT, 0),
'onlyids' => new external_value(PARAM_BOOL, 'Do not return all user fields - only the id and flags (requiresgrading etc)', VALUE_DEFAULT, false),
)
);
}
/**
* Trigger the grading_table_viewed event.
*
* @param int $assignid the assign instance id
* @param int $groupid the current group id
* @param string $filter search string to filter the results.
* @param int $skip Number of records to skip
* @param int $limit Maximum number of records to return
* @return array of warnings and status result
* @since Moodle 3.0
* @throws moodle_exception
*/
public static function list_participants($assignid, $groupid, $filter, $skip, $limit) {
global $DB, $CFG;
require_once($CFG->dirroot . "/mod/assign/locallib.php");
require_once($CFG->dirroot . "/user/lib.php");
$params = self::validate_parameters(self::list_participants_parameters(),
array(
'assignid' => $assignid,
'groupid' => $groupid,
'filter' => $filter,
'skip' => $skip,
'limit' => $limit
));
$warnings = array();
// Request and permission validation.
$assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
$context = context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/assign:view', $context);
$assign = new assign($context, null, null);
$assign->require_view_grades();
$participants = $assign->list_participants_with_filter_status_and_group($params['groupid']);
$result = array();
$index = 0;
foreach ($participants as $record) {
// Preserve the fullname set by the assignment.
$fullname = $record->fullname;
$searchable = $fullname;
$match = false;
if (empty($filter)) {
$match = true;
} else {
$filter = core_text::strtolower($filter);
$value = core_text::strtolower($searchable);
if (is_string($value) && (core_text::strpos($value, $filter) !== false)) {
$match = true;
}
}
if ($match) {
$index++;
if ($index <= $params['skip']) {
continue;
}
if (($params['limit'] > 0) && (($index - $params['skip']) > $params['limit'])) {
break;
}
// Now we do the expensive lookup of user details because we completed the filtering.
if (!$assign->is_blind_marking() && !$params['onlyids']) {
$userdetails = user_get_user_details($record, $course);
} else {
$userdetails = array('id' => $record->id);
}
$userdetails['fullname'] = $fullname;
$userdetails['submitted'] = $record->submitted;
$userdetails['requiregrading'] = $record->requiregrading;
if (!empty($record->groupid)) {
$userdetails['groupid'] = $record->groupid;
}
if (!empty($record->groupname)) {
$userdetails['groupname'] = $record->groupname;
}
$result[] = $userdetails;
}
}
return $result;
}
/**
* Returns description of method result value
*
* @return external_description
* @since Moodle 3.0
*/
public static function list_participants_returns() {
return new external_multiple_structure(
new external_single_structure(array(
'id' => new external_value(PARAM_INT, 'ID of the user'),
'username' => new external_value(PARAM_RAW, 'Username', VALUE_OPTIONAL),
'firstname' => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL),
'lastname' => new external_value(PARAM_NOTAGS, 'The family name of the user', VALUE_OPTIONAL),
'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
'idnumber' => new external_value(PARAM_NOTAGS, 'The idnumber of the user', VALUE_OPTIONAL),
'email' => new external_value(PARAM_TEXT, 'Email address', VALUE_OPTIONAL),
'address' => new external_value(PARAM_MULTILANG, 'Postal address', VALUE_OPTIONAL),
'phone1' => new external_value(PARAM_NOTAGS, 'Phone 1', VALUE_OPTIONAL),
'phone2' => new external_value(PARAM_NOTAGS, 'Phone 2', VALUE_OPTIONAL),
'icq' => new external_value(PARAM_NOTAGS, 'icq number', VALUE_OPTIONAL),
'skype' => new external_value(PARAM_NOTAGS, 'skype id', VALUE_OPTIONAL),
'yahoo' => new external_value(PARAM_NOTAGS, 'yahoo id', VALUE_OPTIONAL),
'aim' => new external_value(PARAM_NOTAGS, 'aim id', VALUE_OPTIONAL),
'msn' => new external_value(PARAM_NOTAGS, 'msn number', VALUE_OPTIONAL),
'department' => new external_value(PARAM_TEXT, 'department', VALUE_OPTIONAL),
'institution' => new external_value(PARAM_TEXT, 'institution', VALUE_OPTIONAL),
'interests' => new external_value(PARAM_TEXT, 'user interests (separated by commas)', VALUE_OPTIONAL),
'firstaccess' => new external_value(PARAM_INT, 'first access to the site (0 if never)', VALUE_OPTIONAL),
'lastaccess' => new external_value(PARAM_INT, 'last access to the site (0 if never)', VALUE_OPTIONAL),
'description' => new external_value(PARAM_RAW, 'User profile description', VALUE_OPTIONAL),
'descriptionformat' => new external_value(PARAM_INT, 'User profile description format', VALUE_OPTIONAL),
'city' => new external_value(PARAM_NOTAGS, 'Home city of the user', VALUE_OPTIONAL),
'url' => new external_value(PARAM_URL, 'URL of the user', VALUE_OPTIONAL),
'country' => new external_value(PARAM_ALPHA, 'Country code of the user, such as AU or CZ', VALUE_OPTIONAL),
'profileimageurlsmall' => new external_value(PARAM_URL, 'User image profile URL - small', VALUE_OPTIONAL),
'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big', VALUE_OPTIONAL),
'customfields' => new external_multiple_structure(
new external_single_structure(
array(
'type' => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field'),
)
), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
'groups' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'group id'),
'name' => new external_value(PARAM_RAW, 'group name'),
'description' => new external_value(PARAM_RAW, 'group description'),
)
), 'user groups', VALUE_OPTIONAL),
'roles' => new external_multiple_structure(
new external_single_structure(
array(
'roleid' => new external_value(PARAM_INT, 'role id'),
'name' => new external_value(PARAM_RAW, 'role name'),
'shortname' => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
'sortorder' => new external_value(PARAM_INT, 'role sortorder')
)
), 'user roles', VALUE_OPTIONAL),
'preferences' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_ALPHANUMEXT, 'The name of the preferences'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
)
), 'User preferences', VALUE_OPTIONAL),
'enrolledcourses' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'Id of the course'),
'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
)
), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL),
'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL),
))
);
}
} }

View file

@ -0,0 +1,66 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* An event observer.
*
* @package assignfeedback_editpdf
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf\event;
/**
* Simple task to run the grade cron.
*/
class observer {
/**
* Listen to events and queue the submission for processing.
* @param \mod_assign\event\submission_created $event
*/
public static function submission_created(\mod_assign\event\submission_created $event) {
global $DB;
$submissionid = $event->other['submissionid'];
$submissionattempt = $event->other['submissionattempt'];
$fields = array( 'submissionid' => $submissionid, 'submissionattempt' => $submissionattempt);
$record = (object) $fields;
$exists = $DB->get_records('assignfeedback_editpdf_queue', $fields);
if (!$exists) {
$DB->insert_record('assignfeedback_editpdf_queue', $record);
}
}
/**
* Listen to events and queue the submission for processing.
* @param \mod_assign\event\submission_updated $event
*/
public static function submission_updated(\mod_assign\event\submission_updated $event) {
global $DB;
$submissionid = $event->other['submissionid'];
$submissionattempt = $event->other['submissionattempt'];
$fields = array( 'submissionid' => $submissionid, 'submissionattempt' => $submissionattempt);
$record = (object) $fields;
$exists = $DB->get_records('assignfeedback_editpdf_queue', $fields);
if (!$exists) {
$DB->insert_record('assignfeedback_editpdf_queue', $record);
}
}
}

View file

@ -118,10 +118,7 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
array('id'=>$linkid, 'class'=>'btn', 'href'=>'#')); array('id'=>$linkid, 'class'=>'btn', 'href'=>'#'));
} }
$links = $launcheditorlink; $links = $launcheditorlink;
$html .= '<input type="hidden" name="assignfeedback_editpdf_haschanges" value="false"/>';
$links .= html_writer::tag('div',
get_string('unsavedchanges', 'assignfeedback_editpdf'),
array('class'=>'assignfeedback_editpdf_unsavedchanges warning'));
$html .= html_writer::div($links, 'visibleifjs'); $html .= html_writer::div($links, 'visibleifjs');
$header = get_string('pluginname', 'assignfeedback_editpdf'); $header = get_string('pluginname', 'assignfeedback_editpdf');
@ -129,6 +126,7 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
// Create the page navigation. // Create the page navigation.
$navigation1 = ''; $navigation1 = '';
$navigation2 = ''; $navigation2 = '';
$navigation3 = '';
// Pick the correct arrow icons for right to left mode. // Pick the correct arrow icons for right to left mode.
if (right_to_left()) { if (right_to_left()) {
@ -156,6 +154,7 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
$navigation2 .= $this->render_toolbar_button('comment_search', 'searchcomments', $this->get_shortcut('searchcomments')); $navigation2 .= $this->render_toolbar_button('comment_search', 'searchcomments', $this->get_shortcut('searchcomments'));
$navigation2 = html_writer::div($navigation2, 'navigation-search', array('role'=>'navigation')); $navigation2 = html_writer::div($navigation2, 'navigation-search', array('role'=>'navigation'));
$toolbar1 = ''; $toolbar1 = '';
$toolbar2 = ''; $toolbar2 = '';
$toolbar3 = ''; $toolbar3 = '';
@ -210,7 +209,14 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
$canvas = html_writer::div($loading, 'drawingcanvas'); $canvas = html_writer::div($loading, 'drawingcanvas');
$canvas = html_writer::div($canvas, 'drawingregion'); $canvas = html_writer::div($canvas, 'drawingregion');
$body .= html_writer::div($canvas, 'hideoverflow'); $changesmessage = html_writer::tag('div',
get_string('draftchangessaved', 'assignfeedback_editpdf'),
array('class'=>'assignfeedback_editpdf_unsavedchanges warning label label-info'));
$changesmessage = html_writer::div($changesmessage, 'unsaved-changes');
$canvas .= $changesmessage;
$body .= $canvas;
$footer = ''; $footer = '';

View file

@ -0,0 +1,107 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A scheduled task.
*
* @package assignfeedback_editpdf
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignfeedback_editpdf\task;
use core\task\scheduled_task;
use assignfeedback_editpdf\document_services;
use context_module;
use assign;
/**
* Simple task to run the grade cron.
*/
class convert_submissions extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('preparesubmissionsforannotation', 'assignfeedback_editpdf');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
require_once($CFG->dirroot . '/mod/assign/locallib.php');
$records = $DB->get_records('assignfeedback_editpdf_queue');
$assignmentcache = array();
foreach ($records as $record) {
$submissionid = $record->submissionid;
$submission = $DB->get_record('assign_submission', array('id' => $submissionid), '*', IGNORE_MISSING);
if (!$submission) {
// Submission no longer exists.
$DB->delete_records('assignfeedback_editpdf_queue', array('id' => $record->id));
continue;
}
$assignmentid = $submission->assignment;
$attemptnumber = $record->submissionattempt;
if (empty($assignmentcache[$assignmentid])) {
$cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
$assignment = new assign($context, null, null);
$assignmentcache[$assignmentid] = $assignment;
} else {
$assignment = $assignmentcache[$assignmentid];
}
$users = array();
if ($submission->userid) {
array_push($users, $submission->userid);
} else {
$members = $assignment->get_submission_group_members($submission->groupid, true);
foreach ($members as $memberid) {
array_push($users, $memberid);
}
}
mtrace('Convert ' . count($users) . ' submission attempt(s) for assignment ' . $assignmentid);
foreach ($users as $userid) {
document_services::get_page_images_for_attempt($assignment,
$userid,
$attemptnumber,
true);
document_services::get_page_images_for_attempt($assignment,
$userid,
$attemptnumber,
false);
}
$DB->delete_records('assignfeedback_editpdf_queue', array('id' => $record->id));
}
}
}

View file

@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* EditPDF event handler definition.
*
* @package assignfeedback_editpdf
* @category event
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// List of observers.
$observers = array(
array(
'eventname' => '\mod_assign\event\submission_created',
'callback' => '\assignfeedback_editpdf\event\observer::submission_created',
),
array(
'eventname' => '\mod_assign\event\submission_updated',
'callback' => '\assignfeedback_editpdf\event\observer::submission_updated',
),
);

12
mod/assign/feedback/editpdf/db/install.xml Normal file → Executable file
View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/assign/feedback/editpdf/db" VERSION="20130926" COMMENT="XMLDB file for Moodle mod/assign/feedback/editpdf" <XMLDB PATH="mod/assign/feedback/editpdf/db" VERSION="20160216" COMMENT="XMLDB file for Moodle mod/assign/feedback/editpdf"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd" xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
> >
@ -59,5 +59,15 @@
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/> <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS> </KEYS>
</TABLE> </TABLE>
<TABLE NAME="assignfeedback_editpdf_queue" COMMENT="Queue for processing.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="submissionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="submissionattempt" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
</TABLES> </TABLES>
</XMLDB> </XMLDB>

View file

@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition of core scheduled tasks.
*
* The handlers defined on this file are processed and registered into
* the Moodle DB after any install or upgrade operation. All plugins
* support this.
*
* @package core
* @category task
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/* List of handlers */
$tasks = array(
array(
'classname' => 'assignfeedback_editpdf\task\convert_submissions',
'blocking' => 0,
'minute' => '*/15',
'hour' => '*',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
),
);

View file

@ -30,7 +30,9 @@ defined('MOODLE_INTERNAL') || die();
* @return bool * @return bool
*/ */
function xmldb_assignfeedback_editpdf_upgrade($oldversion) { function xmldb_assignfeedback_editpdf_upgrade($oldversion) {
global $CFG; global $CFG, $DB;
$dbman = $DB->get_manager();
// Moodle v2.8.0 release upgrade line. // Moodle v2.8.0 release upgrade line.
// Put any upgrade step following this. // Put any upgrade step following this.
@ -41,5 +43,29 @@ function xmldb_assignfeedback_editpdf_upgrade($oldversion) {
// Moodle v3.0.0 release upgrade line. // Moodle v3.0.0 release upgrade line.
// Put any upgrade step following this. // Put any upgrade step following this.
if ($oldversion < 2016021600) {
// Define table assignfeedback_editpdf_queue to be created.
$table = new xmldb_table('assignfeedback_editpdf_queue');
// Adding fields to table assignfeedback_editpdf_queue.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('submissionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('submissionattempt', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table assignfeedback_editpdf_queue.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
// Conditionally launch create table for assignfeedback_editpdf_queue.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Editpdf savepoint reached.
upgrade_plugin_savepoint(true, 2016021600, 'assignfeedback', 'editpdf');
}
return true; return true;
} }

View file

@ -85,7 +85,8 @@ $string['test_notexecutable'] = 'The ghostscript points to a file that is not ex
$string['test_ok'] = 'The ghostscript path appears to be OK - please check you can see the message in the image below'; $string['test_ok'] = 'The ghostscript path appears to be OK - please check you can see the message in the image below';
$string['toolbarbutton'] = '{$a->tool} {$a->shortcut}'; $string['toolbarbutton'] = '{$a->tool} {$a->shortcut}';
$string['tool'] = 'Tool'; $string['tool'] = 'Tool';
$string['unsavedchanges'] = 'Unsaved changes';
$string['viewfeedbackonline'] = 'View annotated PDF...'; $string['viewfeedbackonline'] = 'View annotated PDF...';
$string['white'] = 'White'; $string['white'] = 'White';
$string['yellow'] = 'Yellow'; $string['yellow'] = 'Yellow';
$string['draftchangessaved'] = 'Draft annotations saved';
$string['preparesubmissionsforannotation'] = 'Prepare submissions for annotation';

View file

@ -168,21 +168,16 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
$attempt = $grade->attemptnumber; $attempt = $grade->attemptnumber;
} }
$files = document_services::list_compatible_submission_files_for_attempt($this->assignment, $userid, $attempt); $renderer = $PAGE->get_renderer('assignfeedback_editpdf');
// Only show the editor if there was a compatible file submitted.
if (count($files)) {
$renderer = $PAGE->get_renderer('assignfeedback_editpdf'); $widget = $this->get_widget($userid, $grade, false);
$widget = $this->get_widget($userid, $grade, false); $html = $renderer->render($widget);
$mform->addElement('static', 'editpdf', get_string('editpdf', 'assignfeedback_editpdf'), $html);
$html = $renderer->render($widget); $mform->addHelpButton('editpdf', 'editpdf', 'assignfeedback_editpdf');
$mform->addElement('static', 'editpdf', get_string('editpdf', 'assignfeedback_editpdf'), $html); $mform->addElement('hidden', 'editpdf_source_userid', $userid);
$mform->addHelpButton('editpdf', 'editpdf', 'assignfeedback_editpdf'); $mform->setType('editpdf_source_userid', PARAM_INT);
$mform->addElement('hidden', 'editpdf_source_userid', $userid); $mform->setConstant('editpdf_source_userid', $userid);
$mform->setType('editpdf_source_userid', PARAM_INT);
$mform->setConstant('editpdf_source_userid', $userid);
}
} }
/** /**
@ -364,4 +359,12 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
public function get_file_areas() { public function get_file_areas() {
return array(document_services::FINAL_PDF_FILEAREA => $this->get_name()); return array(document_services::FINAL_PDF_FILEAREA => $this->get_name());
} }
/**
* This plugin will inject content into the review panel with javascript.
* @return bool true
*/
public function supports_review_panel() {
return true;
}
} }

View file

@ -12,10 +12,20 @@
cursor: crosshair; cursor: crosshair;
background-repeat: no-repeat; background-repeat: no-repeat;
background-color: #ccc; background-color: #ccc;
margin-left: auto;
margin-right: auto;
}
.assignfeedback_editpdf_widget .moodle-dialogue-bd .drawingregion {
position: inherit;
} }
.assignfeedback_editpdf_widget .drawingregion { .assignfeedback_editpdf_widget .drawingregion {
border: 1px solid #ccc;
left: 1em;
right: 1em;
top: 52px;
bottom: 0px;
position: absolute;
overflow: auto; overflow: auto;
width: 842px; /* A4 portrait should not need a horizontal scrollbar */
} }
.assignfeedback_editpdf_widget { .assignfeedback_editpdf_widget {
@ -32,18 +42,25 @@
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
min-height: 50px; min-height: 50px;
height: 52px;
overflow: auto;
} }
.moodle-dialogue-base .moodle-dialogue.assignfeedback_editpdf_widget .moodle-dialogue-bd { .moodle-dialogue-base .moodle-dialogue.assignfeedback_editpdf_widget .moodle-dialogue-bd {
padding: 0px; padding: 0px;
} }
.assignfeedback_editpdf_unsavedchanges.haschanges{ .assignfeedback_editpdf_widget .assignfeedback_editpdf_unsavedchanges.haschanges{
display: block; display: inline-block;
} }
.assignfeedback_editpdf_unsavedchanges { .assignfeedback_editpdf_widget .assignfeedback_editpdf_unsavedchanges {
display: none; display: none;
margin-top: 1em; position: absolute;
left: 20px;
top: 60px;
}
.dir-rtl .assignfeedback_editpdf_widget .assignfeedback_editpdf_unsavedchanges {
float: right;
} }
.yui3-colourpicker-hidden, .yui3-colourpicker-hidden,
.yui3-commentsearch-hidden, .yui3-commentsearch-hidden,
@ -290,3 +307,31 @@ ul.assignfeedback_editpdf_menu {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
@media (max-width: 899px) {
.assignfeedback_editpdf_widget .pageheader {
height: 104px;
}
.assignfeedback_editpdf_widget .drawingregion {
top: 104px;
}
}
@media (max-width: 767px) {
.assignfeedback_editpdf_widget .drawingregion {
position: inherit;
margin-bottom: 1em;
}
.assignfeedback_editpdf_widget .pageheader {
height: 52px;
}
.assignfeedback_editpdf_widget .drawingregion {
top: 52px;
}
}
@media (max-width: 683px) {
.assignfeedback_editpdf_widget .pageheader {
height: 104px;
}
.assignfeedback_editpdf_widget .drawingregion {
top: 104px;
}
}

View file

@ -50,11 +50,9 @@ Feature: In an assignment, teacher can annotate PDF files during grading
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Edit" "link" in the "Submitted for grading" "table_row" And I click on "Edit" "link" in the "Submitted for grading" "table_row"
And I click on "Grade" "link" in the "Submitted for grading" "table_row" And I click on "Grade" "link" in the "Submitted for grading" "table_row"
And I follow "Launch PDF editor..."
And I change window size to "large"
And I should see "Page 1 of 3" And I should see "Page 1 of 3"
And I click on ".navigate-next-button" "css_element" And I click on ".navigate-next-button" "css_element"
And I should see "Page 2 of 3" And I should see "Page 2 of 3"
@ -62,11 +60,10 @@ Feature: In an assignment, teacher can annotate PDF files during grading
And I click on ".linebutton" "css_element" And I click on ".linebutton" "css_element"
And I click on ".commentcolourbutton" "css_element" And I click on ".commentcolourbutton" "css_element"
And I click on "//img[@alt=\"Blue\"]/parent::button" "xpath_element" And I click on "//img[@alt=\"Blue\"]/parent::button" "xpath_element"
And I change window size to "medium"
And I wait until the page is ready And I wait until the page is ready
And I click on "Close" "button"
And I press "Save changes" And I press "Save changes"
And I should see "The grade changes were saved" And I wait until the page is ready
And I should see "The changes to the grade and feedback were saved"
@javascript @javascript
Scenario: Submit a PDF file as a student in a team and annotate the PDF as a teacher Scenario: Submit a PDF file as a student in a team and annotate the PDF as a teacher
@ -129,17 +126,15 @@ Feature: In an assignment, teacher can annotate PDF files during grading
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Edit" "link" in the "Student 2" "table_row" And I click on "Edit" "link" in the "Student 2" "table_row"
And I click on "Grade" "link" in the "Student 2" "table_row" And I click on "Grade" "link" in the "Student 2" "table_row"
And I follow "Launch PDF editor..." And I click on ".linebutton" "css_element"
And I change window size to "large" And I draw on the pdf
And I click on ".stampbutton" "css_element"
And I click on ".drawingcanvas" "css_element"
And I change window size to "medium"
And I wait until the page is ready
And I click on "Close" "button"
And I press "Save changes" And I press "Save changes"
And I should see "The grade changes were saved" And I should see "The changes to the grade and feedback were saved"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "View annotated PDF..." in the "student2@example.com" "table_row" And I should see "View annotated PDF..." in the "student2@example.com" "table_row"

View file

@ -49,4 +49,33 @@ class behat_assignfeedback_editpdf extends behat_base {
throw new \Moodle\BehatExtension\Exception\SkippedException; throw new \Moodle\BehatExtension\Exception\SkippedException;
} }
} }
/**
* Draw on the pdf.
*
* @When /^I draw on the pdf$/
*/
public function i_draw_on_the_pdf() {
$js = ' (function() {
var instance = M.assignfeedback_editpdf.instance;
var event = { clientX: 100, clientY: 250, preventDefault: function() {} };
instance.edit_start(event);
}()); ';
$this->getSession()->executeScript($js);
sleep(1);
$js = ' (function() {
var instance = M.assignfeedback_editpdf.instance;
var event = { clientX: 150, clientY: 275, preventDefault: function() {} };
instance.edit_move(event);
}()); ';
$this->getSession()->executeScript($js);
sleep(1);
$js = ' (function() {
var instance = M.assignfeedback_editpdf.instance;
var event = { clientX: 200, clientY: 300, preventDefault: function() {} };
instance.edit_end(event);
}()); ';
$this->getSession()->executeScript($js);
sleep(1);
}
} }

View file

@ -50,26 +50,22 @@ Feature: In a group assignment, teacher can annotate PDF files for all users
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade" "link" in the "Submitted for grading" "table_row" And I click on "Grade" "link" in the "Submitted for grading" "table_row"
And I follow "Launch PDF editor..."
And I change window size to "large"
And I click on ".navigate-next-button" "css_element" And I click on ".navigate-next-button" "css_element"
And I click on ".stampbutton" "css_element" And I click on ".stampbutton" "css_element"
And I click on ".drawingcanvas" "css_element" And I draw on the pdf
And I change window size to "medium"
And I wait until the page is ready And I wait until the page is ready
And I click on "Close" "button"
And I press "Save changes" And I press "Save changes"
And I should see "The grade changes were saved" And I should see "The changes to the grade and feedback were saved"
And I press "Ok"
And I click on "Edit settings" "link"
And I log out And I log out
And I log in as "student1" And I log in as "student1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View annotated PDF..." When I follow "View annotated PDF..."
And I change window size to "large"
Then I should see "Annotate PDF" Then I should see "Annotate PDF"
And I change window size to "medium"
And I wait until the page is ready And I wait until the page is ready
And I click on "Close" "button" And I click on "Close" "button"
And I log out And I log out

View file

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->version = 2015111600; $plugin->version = 2016021601;
$plugin->requires = 2015111000; $plugin->requires = 2015111000;
$plugin->component = 'assignfeedback_editpdf'; $plugin->component = 'assignfeedback_editpdf';

View file

@ -42,6 +42,7 @@ var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton', ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton',
DELETEANNOTATIONBUTTON : '.deleteannotationbutton', DELETEANNOTATIONBUTTON : '.deleteannotationbutton',
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges', UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
UNSAVEDCHANGESINPUT : 'input[name="assignfeedback_editpdf_haschanges"]',
STAMPSBUTTON : '.currentstampbutton', STAMPSBUTTON : '.currentstampbutton',
DIALOGUE : '.' + CSS.DIALOGUE DIALOGUE : '.' + CSS.DIALOGUE
}, },
@ -3097,6 +3098,15 @@ EDITOR.prototype = {
*/ */
dialogue : null, dialogue : null,
/**
* The panel used for all action menu displays.
*
* @property type
* @type Y.Node
* @protected
*/
panel : null,
/** /**
* The number of pages in the pdf. * The number of pages in the pdf.
* *
@ -3258,11 +3268,24 @@ EDITOR.prototype = {
link.on('click', this.link_handler, this); link.on('click', this.link_handler, this);
link.on('key', this.link_handler, 'down:13', this); link.on('key', this.link_handler, 'down:13', this);
this.currentedit.start = false; // We call the amd module to see if we can take control of the review panel.
this.currentedit.end = false; require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
if (!this.get('readonly')) { var panelManager = new ReviewPanelManager();
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
} var panel = panelManager.getReviewPanel('assignfeedback_editpdf');
if (panel) {
panel = Y.one(panel);
panel.empty();
link.ancestor('.fitem').hide();
this.open_in_panel(panel);
}
this.currentedit.start = false;
this.currentedit.end = false;
if (!this.get('readonly')) {
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
}
}.bind(this));
} }
}, },
@ -3341,6 +3364,36 @@ EDITOR.prototype = {
return newpoint; return newpoint;
}, },
/**
* Open the edit-pdf editor in the panel in the page instead of a popup.
* @method open_in_panel
*/
open_in_panel : function(panel) {
var drawingcanvas, drawingregion;
this.panel = panel;
panel.append(this.get('body'));
panel.addClass(CSS.DIALOGUE);
this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
this.graphic = new Y.Graphic({render : drawingcanvas});
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
drawingregion.on('scroll', this.move_canvas, this);
if (!this.get('readonly')) {
drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
drawingcanvas.on('gesturemove', this.edit_move, null, this);
drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
this.refresh_button_state();
}
this.load_all_pages();
},
/** /**
* Called to open the pdf editing dialogue. * Called to open the pdf editing dialogue.
* @method link_handler * @method link_handler
@ -3496,14 +3549,18 @@ EDITOR.prototype = {
try { try {
data = Y.JSON.parse(responsetext); data = Y.JSON.parse(responsetext);
if (data.error || !data.pagecount) { if (data.error || !data.pagecount) {
this.dialogue.hide(); if (this.dialogue) {
this.dialogue.hide();
}
// Display alert dialogue. // Display alert dialogue.
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') }); error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
error.show(); error.show();
return; return;
} }
} catch (e) { } catch (e) {
this.dialogue.hide(); if (this.dialogue) {
this.dialogue.hide();
}
// Display alert dialogue. // Display alert dialogue.
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')}); error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
error.show(); error.show();
@ -3734,7 +3791,11 @@ EDITOR.prototype = {
* @method get_dialogue_element * @method get_dialogue_element
*/ */
get_dialogue_element : function(selector) { get_dialogue_element : function(selector) {
return this.dialogue.get('boundingBox').one(selector); if (this.panel) {
return this.panel.one(selector);
} else {
return this.dialogue.get('boundingBox').one(selector);
}
}, },
/** /**
@ -3915,10 +3976,12 @@ EDITOR.prototype = {
resize : function() { resize : function() {
var drawingregion, drawregionheight; var drawingregion, drawregionheight;
if (!this.dialogue.get('visible')) { if (this.dialogue) {
return; if (!this.dialogue.get('visible')) {
return;
}
this.dialogue.centerDialogue();
} }
this.dialogue.centerDialogue();
// Make sure the dialogue box is not bigger than the max height of the viewport. // Make sure the dialogue box is not bigger than the max height of the viewport.
drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar. drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
@ -3926,7 +3989,9 @@ EDITOR.prototype = {
drawregionheight = 100; drawregionheight = 100;
} }
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
drawingregion.setStyle('maxHeight', drawregionheight +'px'); if (this.dialogue) {
drawingregion.setStyle('maxHeight', drawregionheight +'px');
}
this.redraw(); this.redraw();
return true; return true;
}, },
@ -3985,8 +4050,16 @@ EDITOR.prototype = {
if (jsondata.error) { if (jsondata.error) {
return new M.core.ajaxException(jsondata); return new M.core.ajaxException(jsondata);
} }
Y.one('#' + this.get('linkid')).siblings(SELECTOR.UNSAVEDCHANGESDIV) Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
.item(0).addClass('haschanges'); Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('opacity', 1);
Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'inline-block');
Y.one(SELECTOR.UNSAVEDCHANGESDIV).transition({
duration: 1,
delay: 2,
opacity: 0
}, function() {
Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'none');
});
} catch (e) { } catch (e) {
return new M.core.exception(e); return new M.core.exception(e);
} }
@ -4229,7 +4302,8 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
* @param {Object} params * @param {Object} params
*/ */
M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) { M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
return new EDITOR(params); M.assignfeedback_editpdf.instance = new EDITOR(params);
return M.assignfeedback_editpdf.instance;
}; };
@ -4243,6 +4317,7 @@ M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || f
"json", "json",
"event-move", "event-move",
"event-resize", "event-resize",
"transition",
"querystring-stringify-simple", "querystring-stringify-simple",
"moodle-core-notification-dialog", "moodle-core-notification-dialog",
"moodle-core-notification-exception", "moodle-core-notification-exception",

View file

@ -42,6 +42,7 @@ var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton', ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton',
DELETEANNOTATIONBUTTON : '.deleteannotationbutton', DELETEANNOTATIONBUTTON : '.deleteannotationbutton',
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges', UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
UNSAVEDCHANGESINPUT : 'input[name="assignfeedback_editpdf_haschanges"]',
STAMPSBUTTON : '.currentstampbutton', STAMPSBUTTON : '.currentstampbutton',
DIALOGUE : '.' + CSS.DIALOGUE DIALOGUE : '.' + CSS.DIALOGUE
}, },
@ -3097,6 +3098,15 @@ EDITOR.prototype = {
*/ */
dialogue : null, dialogue : null,
/**
* The panel used for all action menu displays.
*
* @property type
* @type Y.Node
* @protected
*/
panel : null,
/** /**
* The number of pages in the pdf. * The number of pages in the pdf.
* *
@ -3258,11 +3268,24 @@ EDITOR.prototype = {
link.on('click', this.link_handler, this); link.on('click', this.link_handler, this);
link.on('key', this.link_handler, 'down:13', this); link.on('key', this.link_handler, 'down:13', this);
this.currentedit.start = false; // We call the amd module to see if we can take control of the review panel.
this.currentedit.end = false; require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
if (!this.get('readonly')) { var panelManager = new ReviewPanelManager();
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
} var panel = panelManager.getReviewPanel('assignfeedback_editpdf');
if (panel) {
panel = Y.one(panel);
panel.empty();
link.ancestor('.fitem').hide();
this.open_in_panel(panel);
}
this.currentedit.start = false;
this.currentedit.end = false;
if (!this.get('readonly')) {
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
}
}.bind(this));
} }
}, },
@ -3341,6 +3364,36 @@ EDITOR.prototype = {
return newpoint; return newpoint;
}, },
/**
* Open the edit-pdf editor in the panel in the page instead of a popup.
* @method open_in_panel
*/
open_in_panel : function(panel) {
var drawingcanvas, drawingregion;
this.panel = panel;
panel.append(this.get('body'));
panel.addClass(CSS.DIALOGUE);
this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
this.graphic = new Y.Graphic({render : drawingcanvas});
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
drawingregion.on('scroll', this.move_canvas, this);
if (!this.get('readonly')) {
drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
drawingcanvas.on('gesturemove', this.edit_move, null, this);
drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
this.refresh_button_state();
}
this.load_all_pages();
},
/** /**
* Called to open the pdf editing dialogue. * Called to open the pdf editing dialogue.
* @method link_handler * @method link_handler
@ -3496,14 +3549,18 @@ EDITOR.prototype = {
try { try {
data = Y.JSON.parse(responsetext); data = Y.JSON.parse(responsetext);
if (data.error || !data.pagecount) { if (data.error || !data.pagecount) {
this.dialogue.hide(); if (this.dialogue) {
this.dialogue.hide();
}
// Display alert dialogue. // Display alert dialogue.
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') }); error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
error.show(); error.show();
return; return;
} }
} catch (e) { } catch (e) {
this.dialogue.hide(); if (this.dialogue) {
this.dialogue.hide();
}
// Display alert dialogue. // Display alert dialogue.
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')}); error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
error.show(); error.show();
@ -3734,7 +3791,11 @@ EDITOR.prototype = {
* @method get_dialogue_element * @method get_dialogue_element
*/ */
get_dialogue_element : function(selector) { get_dialogue_element : function(selector) {
return this.dialogue.get('boundingBox').one(selector); if (this.panel) {
return this.panel.one(selector);
} else {
return this.dialogue.get('boundingBox').one(selector);
}
}, },
/** /**
@ -3915,10 +3976,12 @@ EDITOR.prototype = {
resize : function() { resize : function() {
var drawingregion, drawregionheight; var drawingregion, drawregionheight;
if (!this.dialogue.get('visible')) { if (this.dialogue) {
return; if (!this.dialogue.get('visible')) {
return;
}
this.dialogue.centerDialogue();
} }
this.dialogue.centerDialogue();
// Make sure the dialogue box is not bigger than the max height of the viewport. // Make sure the dialogue box is not bigger than the max height of the viewport.
drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar. drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
@ -3926,7 +3989,9 @@ EDITOR.prototype = {
drawregionheight = 100; drawregionheight = 100;
} }
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
drawingregion.setStyle('maxHeight', drawregionheight +'px'); if (this.dialogue) {
drawingregion.setStyle('maxHeight', drawregionheight +'px');
}
this.redraw(); this.redraw();
return true; return true;
}, },
@ -3985,8 +4050,16 @@ EDITOR.prototype = {
if (jsondata.error) { if (jsondata.error) {
return new M.core.ajaxException(jsondata); return new M.core.ajaxException(jsondata);
} }
Y.one('#' + this.get('linkid')).siblings(SELECTOR.UNSAVEDCHANGESDIV) Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
.item(0).addClass('haschanges'); Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('opacity', 1);
Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'inline-block');
Y.one(SELECTOR.UNSAVEDCHANGESDIV).transition({
duration: 1,
delay: 2,
opacity: 0
}, function() {
Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'none');
});
} catch (e) { } catch (e) {
return new M.core.exception(e); return new M.core.exception(e);
} }
@ -4229,7 +4302,8 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
* @param {Object} params * @param {Object} params
*/ */
M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) { M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
return new EDITOR(params); M.assignfeedback_editpdf.instance = new EDITOR(params);
return M.assignfeedback_editpdf.instance;
}; };
@ -4243,6 +4317,7 @@ M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || f
"json", "json",
"event-move", "event-move",
"event-resize", "event-resize",
"transition",
"querystring-stringify-simple", "querystring-stringify-simple",
"moodle-core-notification-dialog", "moodle-core-notification-dialog",
"moodle-core-notification-exception", "moodle-core-notification-exception",

View file

@ -42,6 +42,15 @@ EDITOR.prototype = {
*/ */
dialogue : null, dialogue : null,
/**
* The panel used for all action menu displays.
*
* @property type
* @type Y.Node
* @protected
*/
panel : null,
/** /**
* The number of pages in the pdf. * The number of pages in the pdf.
* *
@ -203,11 +212,24 @@ EDITOR.prototype = {
link.on('click', this.link_handler, this); link.on('click', this.link_handler, this);
link.on('key', this.link_handler, 'down:13', this); link.on('key', this.link_handler, 'down:13', this);
this.currentedit.start = false; // We call the amd module to see if we can take control of the review panel.
this.currentedit.end = false; require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
if (!this.get('readonly')) { var panelManager = new ReviewPanelManager();
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
} var panel = panelManager.getReviewPanel('assignfeedback_editpdf');
if (panel) {
panel = Y.one(panel);
panel.empty();
link.ancestor('.fitem').hide();
this.open_in_panel(panel);
}
this.currentedit.start = false;
this.currentedit.end = false;
if (!this.get('readonly')) {
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
}
}.bind(this));
} }
}, },
@ -286,6 +308,36 @@ EDITOR.prototype = {
return newpoint; return newpoint;
}, },
/**
* Open the edit-pdf editor in the panel in the page instead of a popup.
* @method open_in_panel
*/
open_in_panel : function(panel) {
var drawingcanvas, drawingregion;
this.panel = panel;
panel.append(this.get('body'));
panel.addClass(CSS.DIALOGUE);
this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
this.graphic = new Y.Graphic({render : drawingcanvas});
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
drawingregion.on('scroll', this.move_canvas, this);
if (!this.get('readonly')) {
drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
drawingcanvas.on('gesturemove', this.edit_move, null, this);
drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
this.refresh_button_state();
}
this.load_all_pages();
},
/** /**
* Called to open the pdf editing dialogue. * Called to open the pdf editing dialogue.
* @method link_handler * @method link_handler
@ -441,14 +493,18 @@ EDITOR.prototype = {
try { try {
data = Y.JSON.parse(responsetext); data = Y.JSON.parse(responsetext);
if (data.error || !data.pagecount) { if (data.error || !data.pagecount) {
this.dialogue.hide(); if (this.dialogue) {
this.dialogue.hide();
}
// Display alert dialogue. // Display alert dialogue.
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') }); error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
error.show(); error.show();
return; return;
} }
} catch (e) { } catch (e) {
this.dialogue.hide(); if (this.dialogue) {
this.dialogue.hide();
}
// Display alert dialogue. // Display alert dialogue.
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')}); error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
error.show(); error.show();
@ -679,7 +735,11 @@ EDITOR.prototype = {
* @method get_dialogue_element * @method get_dialogue_element
*/ */
get_dialogue_element : function(selector) { get_dialogue_element : function(selector) {
return this.dialogue.get('boundingBox').one(selector); if (this.panel) {
return this.panel.one(selector);
} else {
return this.dialogue.get('boundingBox').one(selector);
}
}, },
/** /**
@ -860,10 +920,12 @@ EDITOR.prototype = {
resize : function() { resize : function() {
var drawingregion, drawregionheight; var drawingregion, drawregionheight;
if (!this.dialogue.get('visible')) { if (this.dialogue) {
return; if (!this.dialogue.get('visible')) {
return;
}
this.dialogue.centerDialogue();
} }
this.dialogue.centerDialogue();
// Make sure the dialogue box is not bigger than the max height of the viewport. // Make sure the dialogue box is not bigger than the max height of the viewport.
drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar. drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
@ -871,7 +933,9 @@ EDITOR.prototype = {
drawregionheight = 100; drawregionheight = 100;
} }
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
drawingregion.setStyle('maxHeight', drawregionheight +'px'); if (this.dialogue) {
drawingregion.setStyle('maxHeight', drawregionheight +'px');
}
this.redraw(); this.redraw();
return true; return true;
}, },
@ -930,8 +994,16 @@ EDITOR.prototype = {
if (jsondata.error) { if (jsondata.error) {
return new M.core.ajaxException(jsondata); return new M.core.ajaxException(jsondata);
} }
Y.one('#' + this.get('linkid')).siblings(SELECTOR.UNSAVEDCHANGESDIV) Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
.item(0).addClass('haschanges'); Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('opacity', 1);
Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'inline-block');
Y.one(SELECTOR.UNSAVEDCHANGESDIV).transition({
duration: 1,
delay: 2,
opacity: 0
}, function() {
Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'none');
});
} catch (e) { } catch (e) {
return new M.core.exception(e); return new M.core.exception(e);
} }
@ -1174,5 +1246,6 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
* @param {Object} params * @param {Object} params
*/ */
M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) { M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
return new EDITOR(params); M.assignfeedback_editpdf.instance = new EDITOR(params);
return M.assignfeedback_editpdf.instance;
}; };

View file

@ -40,6 +40,7 @@ var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton', ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton',
DELETEANNOTATIONBUTTON : '.deleteannotationbutton', DELETEANNOTATIONBUTTON : '.deleteannotationbutton',
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges', UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
UNSAVEDCHANGESINPUT : 'input[name="assignfeedback_editpdf_haschanges"]',
STAMPSBUTTON : '.currentstampbutton', STAMPSBUTTON : '.currentstampbutton',
DIALOGUE : '.' + CSS.DIALOGUE DIALOGUE : '.' + CSS.DIALOGUE
}, },

View file

@ -9,6 +9,7 @@
"json", "json",
"event-move", "event-move",
"event-resize", "event-resize",
"transition",
"querystring-stringify-simple", "querystring-stringify-simple",
"moodle-core-notification-dialog", "moodle-core-notification-dialog",
"moodle-core-notification-exception", "moodle-core-notification-exception",

View file

@ -167,6 +167,15 @@ abstract class assign_feedback_plugin extends assign_plugin {
return ''; return '';
} }
/**
* Supports injecting content into the review panel of the grading app.
*
* @return bool True if this plugin will add content to the review panel of the grading app.
*/
public function supports_review_panel() {
return false;
}
/** /**
* Show a batch operations form * Show a batch operations form
* *

View file

@ -833,16 +833,12 @@ class assign_grading_table extends table_sql implements renderable {
$gradingdisabled = $this->assignment->grading_disabled($row->id); $gradingdisabled = $this->assignment->grading_disabled($row->id);
if (!$this->is_downloading() && $this->hasgrade) { if (!$this->is_downloading() && $this->hasgrade) {
$name = $this->assignment->fullname($row);
$icon = $this->output->pix_icon('gradefeedback',
get_string('gradeuser', 'assign', $name),
'mod_assign');
$urlparams = array('id' => $this->assignment->get_course_module()->id, $urlparams = array('id' => $this->assignment->get_course_module()->id,
'rownum'=>$this->rownum, 'rownum'=> 0,
'action' => 'grade', 'action' => 'grader',
'useridlistid' => $this->assignment->get_useridlist_key_id()); 'userid' => $row->userid);
$url = new moodle_url('/mod/assign/view.php', $urlparams); $url = new moodle_url('/mod/assign/view.php', $urlparams);
$link = $this->output->action_link($url, $icon); $link = '<a href="' . $url . '" class="btn btn-primary">' . get_string('grade') . '</a>';
$grade .= $link . $separator; $grade .= $link . $separator;
} }

View file

@ -101,6 +101,8 @@ $string['batchsetmarkingworkflowstateforusers'] = 'Set marking workflow state fo
$string['blindmarking'] = 'Blind marking'; $string['blindmarking'] = 'Blind marking';
$string['blindmarkingenabledwarning'] = 'Blind marking is enabled for this activity.'; $string['blindmarkingenabledwarning'] = 'Blind marking is enabled for this activity.';
$string['blindmarking_help'] = 'Blind marking hides the identity of students from markers. Blind marking settings will be locked once a submission or grade has been made in relation to this assignment.'; $string['blindmarking_help'] = 'Blind marking hides the identity of students from markers. Blind marking settings will be locked once a submission or grade has been made in relation to this assignment.';
$string['changeuser'] = 'Change user';
$string['changefilters'] = 'Change filters';
$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['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['choosegradingaction'] = 'Grading action';
$string['choosemarker'] = 'Choose...'; $string['choosemarker'] = 'Choose...';
@ -120,6 +122,7 @@ $string['currentgrade'] = 'Current grade in gradebook';
$string['currentattempt'] = 'This is attempt {$a}.'; $string['currentattempt'] = 'This is attempt {$a}.';
$string['currentattemptof'] = 'This is attempt {$a->attemptnumber} ( {$a->maxattempts} attempts allowed ).'; $string['currentattemptof'] = 'This is attempt {$a->attemptnumber} ( {$a->maxattempts} attempts allowed ).';
$string['cutoffdate'] = 'Cut-off date'; $string['cutoffdate'] = 'Cut-off date';
$string['cutoffdatecolon'] = 'Cut-off date: {$a}';
$string['cutoffdate_help'] = 'If set, the assignment will not accept submissions after this date without an extension.'; $string['cutoffdate_help'] = 'If set, the assignment will not accept submissions after this date without an extension.';
$string['cutoffdatevalidation'] = 'The cut-off date cannot be earlier than the due date.'; $string['cutoffdatevalidation'] = 'The cut-off date cannot be earlier than the due date.';
$string['cutoffdatefromdatevalidation'] = 'Cut-off date must be after the allow submissions from date.'; $string['cutoffdatefromdatevalidation'] = 'Cut-off date must be after the allow submissions from date.';
@ -131,6 +134,7 @@ $string['description'] = 'Description';
$string['downloadall'] = 'Download all submissions'; $string['downloadall'] = 'Download all submissions';
$string['download all submissions'] = 'Download all submissions in a zip file.'; $string['download all submissions'] = 'Download all submissions in a zip file.';
$string['duedate'] = 'Due date'; $string['duedate'] = 'Due date';
$string['duedatecolon'] = 'Due date: {$a}';
$string['duedate_help'] = 'This is when the assignment is due. Submissions will still be allowed after this date but any assignments submitted after this date are marked as late. To prevent submissions after a certain date - set the assignment cut off date.'; $string['duedate_help'] = 'This is when the assignment is due. Submissions will still be allowed after this date but any assignments submitted after this date are marked as late. To prevent submissions after a certain date - set the assignment cut off date.';
$string['duedateno'] = 'No due date'; $string['duedateno'] = 'No due date';
$string['submissionempty'] = 'Nothing was submitted'; $string['submissionempty'] = 'Nothing was submitted';
@ -185,6 +189,7 @@ $string['gradersubmissionupdatedsmall'] = '{$a->username} has updated their subm
$string['gradeuser'] = 'Grade {$a}'; $string['gradeuser'] = 'Grade {$a}';
$string['grantextension'] = 'Grant extension'; $string['grantextension'] = 'Grant extension';
$string['grantextensionforusers'] = 'Grant extension for {$a} students'; $string['grantextensionforusers'] = 'Grant extension for {$a} students';
$string['groupsubmissionsettings'] = 'Group submission settings';
$string['enabled'] = 'Enabled'; $string['enabled'] = 'Enabled';
$string['errornosubmissions'] = 'There are no submissions to download'; $string['errornosubmissions'] = 'There are no submissions to download';
$string['errorquickgradingvsadvancedgrading'] = 'The grades were not saved because this assignment is currently using advanced grading'; $string['errorquickgradingvsadvancedgrading'] = 'The grades were not saved because this assignment is currently using advanced grading';
@ -205,6 +210,7 @@ $string['feedbackpluginforgradebook'] = 'Feedback plugin that will push comments
$string['feedbackpluginforgradebook_help'] = 'Only one assignment feedback plugin can push feedback into the gradebook.'; $string['feedbackpluginforgradebook_help'] = 'Only one assignment feedback plugin can push feedback into the gradebook.';
$string['feedbackplugin'] = 'Feedback plugin'; $string['feedbackplugin'] = 'Feedback plugin';
$string['feedbacksettings'] = 'Feedback settings'; $string['feedbacksettings'] = 'Feedback settings';
$string['feedbacktypes'] = 'Feedback types';
$string['filesubmissions'] = 'File submissions'; $string['filesubmissions'] = 'File submissions';
$string['filter'] = 'Filter'; $string['filter'] = 'Filter';
$string['filternone'] = 'No filter'; $string['filternone'] = 'No filter';
@ -223,6 +229,7 @@ $string['gradeoutofhelp_help'] = 'Enter the grade for the student\'s submission
$string['gradestudent'] = 'Grade student: (id={$a->id}, fullname={$a->fullname}). '; $string['gradestudent'] = 'Grade student: (id={$a->id}, fullname={$a->fullname}). ';
$string['grading'] = 'Grading'; $string['grading'] = 'Grading';
$string['gradingchangessaved'] = 'The grade changes were saved'; $string['gradingchangessaved'] = 'The grade changes were saved';
$string['gradechangessaveddetail'] = 'The changes to the grade and feedback were saved';
$string['gradingmethodpreview'] = 'Grading criteria'; $string['gradingmethodpreview'] = 'Grading criteria';
$string['gradingoptions'] = 'Options'; $string['gradingoptions'] = 'Options';
$string['gradingstatus'] = 'Grading status'; $string['gradingstatus'] = 'Grading status';
@ -239,6 +246,7 @@ $string['lastmodifiedsubmission'] = 'Last modified (submission)';
$string['lastmodifiedgrade'] = 'Last modified (grade)'; $string['lastmodifiedgrade'] = 'Last modified (grade)';
$string['latesubmissions'] = 'Late submissions'; $string['latesubmissions'] = 'Late submissions';
$string['latesubmissionsaccepted'] = 'Allowed until {$a}'; $string['latesubmissionsaccepted'] = 'Allowed until {$a}';
$string['loading'] = 'Loading...';
$string['locksubmissionforstudent'] = 'Prevent any more submissions for student: (id={$a->id}, fullname={$a->fullname}).'; $string['locksubmissionforstudent'] = 'Prevent any more submissions for student: (id={$a->id}, fullname={$a->fullname}).';
$string['locksubmissions'] = 'Lock submissions'; $string['locksubmissions'] = 'Lock submissions';
$string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins'; $string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins';
@ -284,6 +292,7 @@ $string['multipleteamsgrader'] = 'Member of more than one group, so unable to ma
$string['mysubmission'] = 'My submission: '; $string['mysubmission'] = 'My submission: ';
$string['newsubmissions'] = 'Assignments submitted'; $string['newsubmissions'] = 'Assignments submitted';
$string['noattempt'] = 'No attempt'; $string['noattempt'] = 'No attempt';
$string['nofilters'] = 'No filters';
$string['nofiles'] = 'No files. '; $string['nofiles'] = 'No files. ';
$string['nograde'] = 'No grade. '; $string['nograde'] = 'No grade. ';
$string['nolatesubmissions'] = 'No late submissions accepted. '; $string['nolatesubmissions'] = 'No late submissions accepted. ';
@ -299,6 +308,7 @@ $string['notgradedyet'] = 'Not graded yet';
$string['notsubmittedyet'] = 'Not submitted yet'; $string['notsubmittedyet'] = 'Not submitted yet';
$string['notifications'] = 'Notifications'; $string['notifications'] = 'Notifications';
$string['nousersselected'] = 'No users selected'; $string['nousersselected'] = 'No users selected';
$string['nousers'] = 'No users';
$string['numberofdraftsubmissions'] = 'Drafts'; $string['numberofdraftsubmissions'] = 'Drafts';
$string['numberofparticipants'] = 'Participants'; $string['numberofparticipants'] = 'Participants';
$string['numberofsubmittedassignments'] = 'Submitted'; $string['numberofsubmittedassignments'] = 'Submitted';
@ -311,6 +321,7 @@ $string['overdue'] = '<font color="red">Assignment is overdue by: {$a}</font>';
$string['outlinegrade'] = 'Grade: {$a}'; $string['outlinegrade'] = 'Grade: {$a}';
$string['page-mod-assign-x'] = 'Any assignment module page'; $string['page-mod-assign-x'] = 'Any assignment module page';
$string['page-mod-assign-view'] = 'Assignment module main and submission page'; $string['page-mod-assign-view'] = 'Assignment module main and submission page';
$string['paramtimeremaining'] = '{$a} remaining';
$string['participant'] = 'Participant'; $string['participant'] = 'Participant';
$string['pluginadministration'] = 'Assignment administration'; $string['pluginadministration'] = 'Assignment administration';
$string['pluginname'] = 'Assignment'; $string['pluginname'] = 'Assignment';
@ -335,10 +346,12 @@ $string['reverttodraftforstudent'] = 'Revert submission to draft for student: (i
$string['reverttodraft'] = 'Revert the submission to draft status.'; $string['reverttodraft'] = 'Revert the submission to draft status.';
$string['reverttodraftshort'] = 'Revert the submission to draft'; $string['reverttodraftshort'] = 'Revert the submission to draft';
$string['reviewed'] = 'Reviewed'; $string['reviewed'] = 'Reviewed';
$string['saveallquickgradingchanges'] = 'Save all quick grading changes';
$string['saveandcontinue'] = 'Save and continue';
$string['savechanges'] = 'Save changes'; $string['savechanges'] = 'Save changes';
$string['savegradingresult'] = 'Grade'; $string['savegradingresult'] = 'Grade';
$string['saveallquickgradingchanges'] = 'Save all quick grading changes';
$string['savenext'] = 'Save and show next'; $string['savenext'] = 'Save and show next';
$string['savingchanges'] = 'Saving changes...';
$string['scale'] = 'Scale'; $string['scale'] = 'Scale';
$string['search:activity'] = 'Assignment activities'; $string['search:activity'] = 'Assignment activities';
$string['sendstudentnotificationsdefault'] = 'Default setting for "Notify students"'; $string['sendstudentnotificationsdefault'] = 'Default setting for "Notify students"';
@ -375,6 +388,7 @@ $string['submissioncopiedsmall'] = 'You have copied your previous assignment sub
$string['submissiondrafts'] = 'Require students click submit button'; $string['submissiondrafts'] = 'Require students click submit button';
$string['submissiondrafts_help'] = 'If enabled, students will have to click a Submit button to declare their submission as final. This allows students to keep a draft version of the submission on the system. If this setting is changed from "No" to "Yes" after students have already submitted those submissions will be regarded as final.'; $string['submissiondrafts_help'] = 'If enabled, students will have to click a Submit button to declare their submission as final. This allows students to keep a draft version of the submission on the system. If this setting is changed from "No" to "Yes" after students have already submitted those submissions will be regarded as final.';
$string['submissioneditable'] = 'Student can edit this submission'; $string['submissioneditable'] = 'Student can edit this submission';
$string['submissionlog'] = 'Student: {$a->fullname}, Status: {$a->status}';
$string['submissionnotcopiedinvalidstatus'] = 'The submission was not copied because it has been edited since it was reopened.'; $string['submissionnotcopiedinvalidstatus'] = 'The submission was not copied because it has been edited since it was reopened.';
$string['submissionnoteditable'] = 'Student cannot edit this submission'; $string['submissionnoteditable'] = 'Student cannot edit this submission';
$string['submissionnotready'] = 'This assignment is not ready to submit:'; $string['submissionnotready'] = 'This assignment is not ready to submit:';
@ -419,6 +433,7 @@ $string['submissionstatus'] = 'Submission status';
$string['submissionstatus_submitted'] = 'Submitted for grading'; $string['submissionstatus_submitted'] = 'Submitted for grading';
$string['submissionsummary'] = '{$a->status}. Last modified on {$a->timemodified}'; $string['submissionsummary'] = '{$a->status}. Last modified on {$a->timemodified}';
$string['submissionteam'] = 'Group'; $string['submissionteam'] = 'Group';
$string['submissiontypes'] = 'Submission types';
$string['submission'] = 'Submission'; $string['submission'] = 'Submission';
$string['submitaction'] = 'Submit'; $string['submitaction'] = 'Submit';
$string['submitforgrading'] = 'Submit for grading'; $string['submitforgrading'] = 'Submit for grading';
@ -439,11 +454,15 @@ $string['teamsubmissiongroupingid_help'] = 'This is the grouping that the assign
$string['textinstructions'] = 'Assignment instructions'; $string['textinstructions'] = 'Assignment instructions';
$string['timemodified'] = 'Last modified'; $string['timemodified'] = 'Last modified';
$string['timeremaining'] = 'Time remaining'; $string['timeremaining'] = 'Time remaining';
$string['timeremainingcolon'] = 'Time remaining: {$a}';
$string['togglezoom'] = 'Zoom in/out of region';
$string['ungroupedusers'] = 'The setting \'Require group to make submission\' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions.'; $string['ungroupedusers'] = 'The setting \'Require group to make submission\' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions.';
$string['unlocksubmissionforstudent'] = 'Allow submissions for student: (id={$a->id}, fullname={$a->fullname}).'; $string['unlocksubmissionforstudent'] = 'Allow submissions for student: (id={$a->id}, fullname={$a->fullname}).';
$string['unlocksubmissions'] = 'Unlock submissions'; $string['unlocksubmissions'] = 'Unlock submissions';
$string['unlimitedattempts'] = 'Unlimited'; $string['unlimitedattempts'] = 'Unlimited';
$string['unlimitedattemptsallowed'] = 'Unlimited attempts allowed.'; $string['unlimitedattemptsallowed'] = 'Unlimited attempts allowed.';
$string['unsavedchanges'] = 'Unsaved changes';
$string['unsavedchangesquestion'] = 'There are unsaved changes to grades or feedback. Do you want to save the changes and continue?';
$string['updategrade'] = 'Update grade'; $string['updategrade'] = 'Update grade';
$string['updatetable'] = 'Save and update table'; $string['updatetable'] = 'Save and update table';
$string['upgradenotimplemented'] = 'Upgrade not implemented in plugin ({$a->type} {$a->subtype})'; $string['upgradenotimplemented'] = 'Upgrade not implemented in plugin ({$a->type} {$a->subtype})';
@ -452,6 +471,7 @@ $string['useridlistnotcached'] = 'The grade changes were NOT saved, as it was no
$string['userswhoneedtosubmit'] = 'Users who need to submit: {$a}'; $string['userswhoneedtosubmit'] = 'Users who need to submit: {$a}';
$string['usergrade'] = 'User grade'; $string['usergrade'] = 'User grade';
$string['validmarkingworkflowstates'] = 'Valid marking workflow states'; $string['validmarkingworkflowstates'] = 'Valid marking workflow states';
$string['viewadifferentattempt'] = 'View a different attempt';
$string['viewbatchsetmarkingworkflowstate'] = 'View batch set marking workflow state page.'; $string['viewbatchsetmarkingworkflowstate'] = 'View batch set marking workflow state page.';
$string['viewbatchmarkingallocation'] = 'View batch set marking allocation page.'; $string['viewbatchmarkingallocation'] = 'View batch set marking allocation page.';
$string['viewfeedback'] = 'View feedback'; $string['viewfeedback'] = 'View feedback';
@ -459,7 +479,7 @@ $string['viewfeedbackforuser'] = 'View feedback for user: {$a}';
$string['viewfullgradingpage'] = 'Open the full grading page to provide feedback'; $string['viewfullgradingpage'] = 'Open the full grading page to provide feedback';
$string['viewgradebook'] = 'View gradebook'; $string['viewgradebook'] = 'View gradebook';
$string['viewgradingformforstudent'] = 'View grading page for student: (id={$a->id}, fullname={$a->fullname}).'; $string['viewgradingformforstudent'] = 'View grading page for student: (id={$a->id}, fullname={$a->fullname}).';
$string['viewgrading'] = 'View/grade all submissions'; $string['viewgrading'] = 'View all submissions';
$string['viewownsubmissionform'] = 'View own submit assignment page.'; $string['viewownsubmissionform'] = 'View own submit assignment page.';
$string['viewownsubmissionstatus'] = 'View own submission status page.'; $string['viewownsubmissionstatus'] = 'View own submission status page.';
$string['viewsubmissionforuser'] = 'View submission for user: {$a}'; $string['viewsubmissionforuser'] = 'View submission for user: {$a}';
@ -469,7 +489,4 @@ $string['viewsummary'] = 'View summary';
$string['viewsubmissiongradingtable'] = 'View submission grading table.'; $string['viewsubmissiongradingtable'] = 'View submission grading table.';
$string['viewrevealidentitiesconfirm'] = 'View reveal student identities confirmation page.'; $string['viewrevealidentitiesconfirm'] = 'View reveal student identities confirmation page.';
$string['workflowfilter'] = 'Workflow filter'; $string['workflowfilter'] = 'Workflow filter';
$string['submissiontypes'] = 'Submission types'; $string['xofy'] = '{$a->x} of {$a->y}';
$string['feedbacktypes'] = 'Feedback types';
$string['groupsubmissionsettings'] = 'Group submission settings';
$string['submissionlog'] = 'Student: {$a->fullname}, Status: {$a->status}';

View file

@ -1474,3 +1474,36 @@ function assign_pluginfile($course,
} }
send_stored_file($file, 0, 0, $forcedownload, $options); send_stored_file($file, 0, 0, $forcedownload, $options);
} }
/**
* Serve the grading panel as a fragment.
*
* @param array $args List of named arguments for the fragment loader.
* @return string
*/
function mod_assign_output_fragment_gradingpanel($args) {
global $CFG;
$context = $args['context'];
if ($context->contextlevel != CONTEXT_MODULE) {
return null;
}
require_once($CFG->dirroot . '/mod/assign/locallib.php');
$assign = new assign($context, null, null);
$userid = clean_param($args['userid'], PARAM_INT);
$attemptnumber = clean_param($args['attemptnumber'], PARAM_INT);
$formdata = array();
if (!empty($args['jsonformdata'])) {
$serialiseddata = json_decode($args['jsonformdata']);
parse_str($serialiseddata, $formdata);
}
$viewargs = array(
'userid' => $userid,
'attemptnumber' => $attemptnumber,
'formdata' => $formdata
);
return $assign->view('gradingpanel', $viewargs);
}

View file

@ -77,6 +77,8 @@ require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
require_once($CFG->libdir . '/eventslib.php'); require_once($CFG->libdir . '/eventslib.php');
require_once($CFG->libdir . '/portfolio/caller.php'); require_once($CFG->libdir . '/portfolio/caller.php');
use \mod_assign\output\grading_app;
/** /**
* Standard base class for mod_assign (assignment types). * Standard base class for mod_assign (assignment types).
* *
@ -203,7 +205,7 @@ class assign {
public function register_return_link($action, $params) { public function register_return_link($action, $params) {
global $PAGE; global $PAGE;
$params['action'] = $action; $params['action'] = $action;
$currenturl = $PAGE->url; $currenturl = new moodle_url('/mod/assign/view.php');
$currenturl->params($params); $currenturl->params($params);
$PAGE->set_url($currenturl); $PAGE->set_url($currenturl);
@ -406,9 +408,10 @@ class assign {
* the settings for the assignment and the status of the assignment. * the settings for the assignment and the status of the assignment.
* *
* @param string $action The current action if any. * @param string $action The current action if any.
* @param array $args Optional arguments to pass to the view (instead of getting them from GET and POST).
* @return string - The page output. * @return string - The page output.
*/ */
public function view($action='') { public function view($action='', $args = array()) {
global $PAGE; global $PAGE;
$o = ''; $o = '';
@ -548,6 +551,8 @@ class assign {
} else if ($action == 'quickgradingresult') { } else if ($action == 'quickgradingresult') {
$mform = null; $mform = null;
$o .= $this->view_quickgrading_result($message); $o .= $this->view_quickgrading_result($message);
} else if ($action == 'gradingpanel') {
$o .= $this->view_single_grading_panel($args);
} else if ($action == 'grade') { } else if ($action == 'grade') {
$o .= $this->view_single_grade_page($mform); $o .= $this->view_single_grade_page($mform);
} else if ($action == 'viewpluginassignfeedback') { } else if ($action == 'viewpluginassignfeedback') {
@ -556,6 +561,8 @@ class assign {
$o .= $this->view_plugin_content('assignsubmission'); $o .= $this->view_plugin_content('assignsubmission');
} else if ($action == 'editsubmission') { } else if ($action == 'editsubmission') {
$o .= $this->view_edit_submission_page($mform, $notices); $o .= $this->view_edit_submission_page($mform, $notices);
} else if ($action == 'grader') {
$o .= $this->view_grader();
} else if ($action == 'grading') { } else if ($action == 'grading') {
$o .= $this->view_grading_page(); $o .= $this->view_grading_page();
} else if ($action == 'downloadall') { } else if ($action == 'downloadall') {
@ -1413,6 +1420,85 @@ class assign {
} }
} }
/**
* Get the submission status/grading status for all submissions in this assignment.
* These statuses match the available filters (requiregrading, submitted, notsubmitted).
* If this is a group assignment, group info is also returned.
*
* @param int $currentgroup
* @param bool $idsonly
* @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'groupid', 'groupname'
*/
public function list_participants_with_filter_status_and_group($currentgroup) {
global $DB;
$participants = $this->list_participants($currentgroup, false);
if (empty($participants)) {
return $participants;
}
list($insql, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
$assignid = $this->get_instance()->id;
$params['assignmentid1'] = $assignid;
$params['assignmentid2'] = $assignid;
$sql = 'SELECT u.id, s.status, s.timemodified AS stime, g.timemodified AS gtime, g.grade FROM {user} u
LEFT JOIN {assign_submission} s
ON u.id = s.userid
AND s.assignment = :assignmentid1
AND s.latest = 1
LEFT JOIN {assign_grades} g
ON u.id = g.userid
AND g.assignment = :assignmentid2
AND g.attemptnumber = s.attemptnumber
WHERE u.id ' . $insql;
$records = $DB->get_records_sql($sql, $params);
if ($this->get_instance()->teamsubmission) {
// Get all groups.
$allgroups = groups_get_all_groups($this->get_course()->id,
array_keys($participants),
$this->get_instance()->teamsubmissiongroupingid,
'DISTINCT g.id, g.name');
}
foreach ($participants as $userid => $participant) {
$participants[$userid]->fullname = $this->fullname($participant);
$participants[$userid]->submitted = false;
$participants[$userid]->requiregrading = false;
}
foreach ($records as $userid => $submissioninfo) {
// These filters are 100% the same as the ones in the grading table SQL.
$submitted = false;
$requiregrading = false;
if (!empty($submissioninfo->stime) && $submissioninfo->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
$submitted = true;
}
if ($submitted && ($submissioninfo->stime >= $submissioninfo->gtime ||
empty($submissioninfo->gtime) ||
$submissioninfo->grade === null)) {
$requiregrading = true;
}
$participants[$userid]->submitted = $submitted;
$participants[$userid]->requiregrading = $requiregrading;
if ($this->get_instance()->teamsubmission) {
$group = $this->get_submission_group($userid);
if ($group) {
$participants[$userid]->groupid = $group->id;
$participants[$userid]->groupname = $group->name;
}
}
}
return $participants;
}
/** /**
* Load a list of users enrolled in the current course with the specified permission and group. * Load a list of users enrolled in the current course with the specified permission and group.
* 0 for no group. * 0 for no group.
@ -1429,7 +1515,11 @@ class assign {
$key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users(); $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
if (!isset($this->participants[$key])) { if (!isset($this->participants[$key])) {
$users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null, $order = 'u.lastname, u.firstname, u.id';
if ($this->is_blind_marking()) {
$order = 'u.id';
}
$users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', $order, null, null,
$this->show_only_active_users()); $this->show_only_active_users());
$cm = $this->get_course_module(); $cm = $this->get_course_module();
@ -2836,7 +2926,7 @@ class assign {
if ($this->output) { if ($this->output) {
return $this->output; return $this->output;
} }
$this->output = $PAGE->get_renderer('mod_assign'); $this->output = $PAGE->get_renderer('mod_assign', null, RENDERER_TARGET_GENERAL);
return $this->output; return $this->output;
} }
@ -3047,6 +3137,166 @@ class assign {
return $DB->get_record('assign_grades', $params, '*', MUST_EXIST); return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
} }
/**
* Print the grading page for a single user submission.
*
* @param array $args Optional args array (better than pulling args from _GET and _POST)
* @return string
*/
protected function view_single_grading_panel($args) {
global $DB, $CFG, $SESSION, $PAGE;
$o = '';
$instance = $this->get_instance();
require_once($CFG->dirroot . '/mod/assign/gradeform.php');
// Need submit permission to submit an assignment.
require_capability('mod/assign:grade', $this->context);
// If userid is passed - we are only grading a single student.
$userid = $args['userid'];
$attemptnumber = $args['attemptnumber'];
$rownum = 0;
$useridlist = array($userid);
$last = true;
// This variation on the url will link direct to this student, with no next/previous links.
// The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
$returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
$this->register_return_link('grade', $returnparams);
$user = $DB->get_record('user', array('id' => $userid));
$submission = $this->get_user_submission($userid, false, $attemptnumber);
$submissiongroup = null;
$teamsubmission = null;
$notsubmitted = array();
if ($instance->teamsubmission) {
$teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
$submissiongroup = $this->get_submission_group($userid);
$groupid = 0;
if ($submissiongroup) {
$groupid = $submissiongroup->id;
}
$notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
}
// Get the requested grade.
$grade = $this->get_user_grade($userid, false, $attemptnumber);
$flags = $this->get_user_flags($userid, false);
if ($this->can_view_submission($userid)) {
$gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
$extensionduedate = null;
if ($flags) {
$extensionduedate = $flags->extensionduedate;
}
$showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
$viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
$usergroups = $this->get_all_groups($user->id);
$submissionstatus = new assign_submission_status_compact($instance->allowsubmissionsfromdate,
$instance->alwaysshowdescription,
$submission,
$instance->teamsubmission,
$teamsubmission,
$submissiongroup,
$notsubmitted,
$this->is_any_submission_plugin_enabled(),
$gradelocked,
$this->is_graded($userid),
$instance->duedate,
$instance->cutoffdate,
$this->get_submission_plugins(),
$this->get_return_action(),
$this->get_return_params(),
$this->get_course_module()->id,
$this->get_course()->id,
assign_submission_status::GRADER_VIEW,
$showedit,
false,
$viewfullnames,
$extensionduedate,
$this->get_context(),
$this->is_blind_marking(),
'',
$instance->attemptreopenmethod,
$instance->maxattempts,
$this->get_grading_status($userid),
$instance->preventsubmissionnotingroup,
$usergroups);
$o .= $this->get_renderer()->render($submissionstatus);
}
if ($grade) {
$data = new stdClass();
if ($grade->grade !== null && $grade->grade >= 0) {
$data->grade = format_float($grade->grade, 2);
}
} else {
$data = new stdClass();
$data->grade = '';
}
if (!empty($flags->workflowstate)) {
$data->workflowstate = $flags->workflowstate;
}
if (!empty($flags->allocatedmarker)) {
$data->allocatedmarker = $flags->allocatedmarker;
}
// Warning if required.
$allsubmissions = $this->get_all_submissions($userid);
if ($attemptnumber != -1 && ($attemptnumber + 1) != count($allsubmissions)) {
$params = array('attemptnumber'=>$attemptnumber + 1,
'totalattempts'=>count($allsubmissions));
$message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
$o .= $this->get_renderer()->notification($message);
}
$pagination = array('rownum'=>$rownum,
'useridlistid'=>0,
'last'=>$last,
'userid'=>$userid,
'attemptnumber'=>$attemptnumber,
'gradingpanel' => true);
if (!empty($args['formdata'])) {
$data = (array) $data;
$data = (object) array_merge($data, $args['formdata']);
}
$formparams = array($this, $data, $pagination);
$mform = new mod_assign_grade_form(null,
$formparams,
'post',
'',
array('class'=>'gradeform'));
if (!empty($args['formdata'])) {
// If we were passed form data - we want the form to check the data
// and show errors.
$mform->is_validated();
}
$o .= $this->get_renderer()->heading(get_string('grade'), 3);
$o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
if (count($allsubmissions) > 1) {
$allgrades = $this->get_all_grades($userid);
$history = new assign_attempt_history_chooser($allsubmissions,
$allgrades,
$this->get_course_module()->id,
$userid);
$o .= $this->get_renderer()->render($history);
}
\mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
return $o;
}
/** /**
* Print the grading page for a single user submission. * Print the grading page for a single user submission.
* *
@ -3195,7 +3445,7 @@ class assign {
// Warning if required. // Warning if required.
$allsubmissions = $this->get_all_submissions($userid); $allsubmissions = $this->get_all_submissions($userid);
if ($attemptnumber != -1) { if ($attemptnumber != -1 && ($attemptnumber + 1) != count($allsubmissions)) {
$params = array('attemptnumber'=>$attemptnumber + 1, $params = array('attemptnumber'=>$attemptnumber + 1,
'totalattempts'=>count($allsubmissions)); 'totalattempts'=>count($allsubmissions));
$message = get_string('editingpreviousfeedbackwarning', 'assign', $params); $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
@ -3485,6 +3735,37 @@ class assign {
return $o; return $o;
} }
/**
* View entire grader app.
*
* @return string
*/
protected function view_grader() {
global $USER, $PAGE;
$o = '';
// Need submit permission to submit an assignment.
$this->require_view_grades();
$PAGE->set_pagelayout('embedded');
$PAGE->set_title($this->get_context()->get_context_name());
$o .= $this->get_renderer()->header();
$userid = optional_param('userid', 0, PARAM_INT);
$currentgroup = groups_get_activity_group($this->get_course_module(), true);
$framegrader = new grading_app($userid, $currentgroup, $this);
$o .= $this->get_renderer()->render($framegrader);
$o .= $this->view_footer();
\mod_assign\event\grading_table_viewed::create_from_assign($this)->trigger();
return $o;
}
/** /**
* View entire grading page. * View entire grading page.
* *
@ -3566,10 +3847,10 @@ class assign {
public function fullname($user) { public function fullname($user) {
if ($this->is_blind_marking()) { if ($this->is_blind_marking()) {
$hasviewblind = has_capability('mod/assign:viewblinddetails', $this->get_context()); $hasviewblind = has_capability('mod/assign:viewblinddetails', $this->get_context());
$uniqueid = $this->get_uniqueid_for_user($user->id);
if ($hasviewblind) { if ($hasviewblind) {
return fullname($user); return get_string('participant', 'assign') . ' ' . $uniqueid . ' (' . fullname($user) . ')';
} else { } else {
$uniqueid = $this->get_uniqueid_for_user($user->id);
return get_string('participant', 'assign') . ' ' . $uniqueid; return get_string('participant', 'assign') . ' ' . $uniqueid;
} }
} else { } else {
@ -3666,15 +3947,10 @@ class assign {
public function can_view_group_submission($groupid) { public function can_view_group_submission($groupid) {
global $USER; global $USER;
if (has_capability('mod/assign:grade', $this->context)) {
return true;
}
if (!is_enrolled($this->get_course_context(), $USER->id)) {
return false;
}
$members = $this->get_submission_group_members($groupid, true); $members = $this->get_submission_group_members($groupid, true);
foreach ($members as $member) { foreach ($members as $member) {
if ($member->id == $USER->id) { // If we can view any members submission, we can view the submission for the group.
if ($this->can_view_submission($member->id)) {
return true; return true;
} }
} }
@ -3693,12 +3969,12 @@ class assign {
if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) { if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
return false; return false;
} }
if (has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
return true;
}
if (!is_enrolled($this->get_course_context(), $userid)) { if (!is_enrolled($this->get_course_context(), $userid)) {
return false; return false;
} }
if (has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
return true;
}
if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) { if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) {
return true; return true;
} }
@ -6226,11 +6502,12 @@ class assign {
global $USER, $CFG, $SESSION; global $USER, $CFG, $SESSION;
$settings = $this->get_instance(); $settings = $this->get_instance();
$rownum = $params['rownum']; $rownum = isset($params['rownum']) ? $params['rownum'] : 0;
$last = $params['last']; $last = isset($params['last']) ? $params['last'] : true;
$useridlistid = $params['useridlistid']; $useridlistid = isset($params['useridlistid']) ? $params['useridlistid'] : 0;
$userid = $params['userid']; $userid = isset($params['userid']) ? $params['userid'] : 0;
$attemptnumber = $params['attemptnumber']; $attemptnumber = isset($params['attemptnumber']) ? $params['attemptnumber'] : 0;
$gradingpanel = !empty($params['gradingpanel']);
if (!$userid) { if (!$userid) {
$useridlistkey = $this->get_useridlist_key($useridlistid); $useridlistkey = $this->get_useridlist_key($useridlistid);
if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) { if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
@ -6373,7 +6650,7 @@ class assign {
$mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE); $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE);
$mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED); $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED);
} }
$gradestring = '<span class="currentgrade">' . $gradestring . '</span>';
$mform->addElement('static', 'currentgrade', get_string('currentgrade', 'assign'), $gradestring); $mform->addElement('static', 'currentgrade', get_string('currentgrade', 'assign'), $gradestring);
if (count($useridlist) > 1) { if (count($useridlist) > 1) {
@ -6431,7 +6708,12 @@ class assign {
$mform->setDefault('addattempt', 0); $mform->setDefault('addattempt', 0);
} }
} }
$mform->addElement('selectyesno', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign')); if (!$gradingpanel) {
$mform->addElement('selectyesno', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
} else {
$mform->addElement('hidden', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
$mform->setType('sendstudentnotifications', PARAM_BOOL);
}
// Get assignment visibility information for student. // Get assignment visibility information for student.
$modinfo = get_fast_modinfo($settings->course, $userid); $modinfo = get_fast_modinfo($settings->course, $userid);
$cm = $modinfo->get_cm($this->get_course_module()->id); $cm = $modinfo->get_cm($this->get_course_module()->id);
@ -6451,29 +6733,32 @@ class assign {
$mform->addElement('hidden', 'action', 'submitgrade'); $mform->addElement('hidden', 'action', 'submitgrade');
$mform->setType('action', PARAM_ALPHA); $mform->setType('action', PARAM_ALPHA);
$buttonarray=array(); if (!$gradingpanel) {
$name = get_string('savechanges', 'assign');
$buttonarray[] = $mform->createElement('submit', 'savegrade', $name);
if (!$last) {
$name = get_string('savenext', 'assign');
$buttonarray[] = $mform->createElement('submit', 'saveandshownext', $name);
}
$buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
$buttonarray=array();
if ($rownum > 0) { $buttonarray=array();
$name = get_string('previous', 'assign'); $name = get_string('savechanges', 'assign');
$buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', $name); $buttonarray[] = $mform->createElement('submit', 'savegrade', $name);
} if (!$last) {
$name = get_string('savenext', 'assign');
$buttonarray[] = $mform->createElement('submit', 'saveandshownext', $name);
}
$buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
$buttonarray=array();
if (!$last) { if ($rownum > 0) {
$name = get_string('nosavebutnext', 'assign'); $name = get_string('previous', 'assign');
$buttonarray[] = $mform->createElement('submit', 'nosaveandnext', $name); $buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', $name);
} }
if (!empty($buttonarray)) {
$mform->addGroup($buttonarray, 'navar', '', array(' '), false); if (!$last) {
$name = get_string('nosavebutnext', 'assign');
$buttonarray[] = $mform->createElement('submit', 'nosaveandnext', $name);
}
if (!empty($buttonarray)) {
$mform->addGroup($buttonarray, 'navar', '', array(' '), false);
}
} }
// The grading form does not work well with shortforms. // The grading form does not work well with shortforms.
$mform->setDisableShortforms(); $mform->setDisableShortforms();
@ -7057,7 +7342,7 @@ class assign {
} else { } else {
$submission = $this->get_user_submission($userid, false, $data->attemptnumber); $submission = $this->get_user_submission($userid, false, $data->attemptnumber);
} }
if ($instance->teamsubmission && $data->applytoall) { if ($instance->teamsubmission && !empty($data->applytoall)) {
$groupid = 0; $groupid = 0;
if ($this->get_submission_group($userid)) { if ($this->get_submission_group($userid)) {
$group = $this->get_submission_group($userid); $group = $this->get_submission_group($userid);

View file

@ -488,6 +488,15 @@ class assign_submission_status implements renderable {
$this->usergroups = $usergroups; $this->usergroups = $usergroups;
} }
} }
/**
* Renderable submission status
* @package mod_assign
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assign_submission_status_compact extends assign_submission_status implements renderable {
// Compact view of the submission status. Not in a table etc.
}
/** /**
* Used to output the attempt history for a particular assignment. * Used to output the attempt history for a particular assignment.
@ -556,6 +565,86 @@ class assign_attempt_history implements renderable {
} }
} }
/**
* Used to output the attempt history chooser for a particular assignment.
*
* @package mod_assign
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assign_attempt_history_chooser implements renderable, templatable {
/** @var array submissions - The list of previous attempts */
public $submissions = array();
/** @var array grades - The grades for the previous attempts */
public $grades = array();
/** @var int coursemoduleid - The cmid for the assignment */
public $coursemoduleid = 0;
/** @var int userid - The current userid */
public $userid = 0;
/**
* Constructor
*
* @param array $submissions
* @param array $grades
* @param int $coursemoduleid
* @param int $userid
*/
public function __construct($submissions,
$grades,
$coursemoduleid,
$userid) {
$this->submissions = $submissions;
$this->grades = $grades;
$this->coursemoduleid = $coursemoduleid;
$this->userid = $userid;
}
/**
* Function to export the renderer data in a format that is suitable for a
* mustache template.
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return stdClass|array
*/
public function export_for_template(renderer_base $output) {
// Show newest to oldest.
$export = (object) $this;
$export->submissions = array_reverse($export->submissions);
$export->submissioncount = count($export->submissions);
foreach ($export->submissions as $i => $submission) {
$grade = null;
foreach ($export->grades as $onegrade) {
if ($onegrade->attemptnumber == $submission->attemptnumber) {
$submission->grade = $onegrade;
break;
}
}
if (!$submission) {
$submission = new stdClass();
}
$editbtn = '';
if ($submission->timemodified) {
$submissionsummary = userdate($submission->timemodified);
} else {
$submissionsummary = get_string('nosubmission', 'assign');
}
$attemptsummaryparams = array('attemptnumber'=>$submission->attemptnumber+1,
'submissionsummary'=>$submissionsummary);
$submission->attemptsummary = get_string('attemptheading', 'assign', $attemptsummaryparams);
$submission->statussummary = get_string('submissionstatus_' . $submission->status, 'assign');
}
return $export;
}
}
/** /**
* Renderable header * Renderable header
* @package mod_assign * @package mod_assign

View file

@ -26,6 +26,8 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/assign/locallib.php'); require_once($CFG->dirroot . '/mod/assign/locallib.php');
use \mod_assign\output\grading_app;
/** /**
* A custom renderer class that extends the plugin_renderer_base and is used by the assign module. * A custom renderer class that extends the plugin_renderer_base and is used by the assign module.
* *
@ -333,14 +335,19 @@ class mod_assign_renderer extends plugin_renderer_base {
$o .= $this->output->box_end(); $o .= $this->output->box_end();
// Link to the grading page. // Link to the grading page.
$o .= $this->output->container_start('submissionlinks'); $o .= '<center>';
$o .= $this->output->container_start('submissionlinks btn-group');
$urlparams = array('id' => $summary->coursemoduleid, 'action'=>'grading'); $urlparams = array('id' => $summary->coursemoduleid, 'action'=>'grading');
$url = new moodle_url('/mod/assign/view.php', $urlparams); $url = new moodle_url('/mod/assign/view.php', $urlparams);
$o .= $this->output->action_link($url, get_string('viewgrading', 'assign')); $o .= '<a href="' . $url . '" class="btn">' . get_string('viewgrading', 'mod_assign') . '</a>';
$urlparams = array('id' => $summary->coursemoduleid, 'action'=>'grader');
$url = new moodle_url('/mod/assign/view.php', $urlparams);
$o .= '<a href="' . $url . '" class="btn btn-primary">' . get_string('grade') . '</a>';
$o .= $this->output->container_end(); $o .= $this->output->container_end();
// Close the container and insert a spacer. // Close the container and insert a spacer.
$o .= $this->output->container_end(); $o .= $this->output->container_end();
$o .= '</center>';
return $o; return $o;
} }
@ -417,6 +424,189 @@ class mod_assign_renderer extends plugin_renderer_base {
return $o; return $o;
} }
/**
* Render a compact view of the current status of the submission.
*
* @param assign_submission_status_compact $status
* @return string
*/
public function render_assign_submission_status_compact(assign_submission_status_compact $status) {
$o = '';
$o .= $this->output->container_start('submissionstatustable');
$o .= $this->output->heading(get_string('submission', 'assign'), 3);
$time = time();
if ($status->teamsubmissionenabled) {
$group = $status->submissiongroup;
if ($group) {
$team = format_string($group->name, false, $status->context);
} else if ($status->preventsubmissionnotingroup) {
if (count($status->usergroups) == 0) {
$team = '<span class="alert alert-error">' . get_string('noteam', 'assign') . '</span>';
} else if (count($status->usergroups) > 1) {
$team = '<span class="alert alert-error">' . get_string('multipleteams', 'assign') . '</span>';
}
} else {
$team = get_string('defaultteam', 'assign');
}
$o .= $this->output->container(get_string('teamname', 'assign', $team), 'teamname');
}
if (!$status->teamsubmissionenabled) {
if ($status->submission && $status->submission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
$statusstr = get_string('submissionstatus_' . $status->submission->status, 'assign');
$o .= $this->output->container($statusstr, 'submissionstatus' . $status->submission->status);
} else {
if (!$status->submissionsenabled) {
$o .= $this->output->container(get_string('noonlinesubmissions', 'assign'), 'submissionstatus');
} else {
$o .= $this->output->container(get_string('noattempt', 'assign'), 'submissionstatus');
}
}
} else {
$group = $status->submissiongroup;
if (!$group && $status->preventsubmissionnotingroup) {
$o .= $this->output->container(get_string('nosubmission', 'assign'), 'submissionstatus');
} else if ($status->teamsubmission && $status->teamsubmission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
$teamstatus = $status->teamsubmission->status;
$submissionsummary = get_string('submissionstatus_' . $teamstatus, 'assign');
$groupid = 0;
if ($status->submissiongroup) {
$groupid = $status->submissiongroup->id;
}
$members = $status->submissiongroupmemberswhoneedtosubmit;
$userslist = array();
foreach ($members as $member) {
$urlparams = array('id' => $member->id, 'course'=>$status->courseid);
$url = new moodle_url('/user/view.php', $urlparams);
if ($status->view == assign_submission_status::GRADER_VIEW && $status->blindmarking) {
$userslist[] = $member->alias;
} else {
$fullname = fullname($member, $status->canviewfullnames);
$userslist[] = $this->output->action_link($url, $fullname);
}
}
if (count($userslist) > 0) {
$userstr = join(', ', $userslist);
$formatteduserstr = get_string('userswhoneedtosubmit', 'assign', $userstr);
$submissionsummary .= $this->output->container($formatteduserstr);
}
$o .= $this->output->container($submissionsummary, 'submissionstatus' . $status->teamsubmission->status);
} else {
if (!$status->submissionsenabled) {
$o .= $this->output->container(get_string('noonlinesubmissions', 'assign'), 'submissionstatus');
} else {
$o .= $this->output->container(get_string('nosubmission', 'assign'), 'submissionstatus');
}
}
}
// Is locked?
if ($status->locked) {
$o .= $this->output->container(get_string('submissionslocked', 'assign'), 'submissionlocked');
}
// Grading status.
$statusstr = '';
$classname = 'gradingstatus';
if ($status->gradingstatus == ASSIGN_GRADING_STATUS_GRADED ||
$status->gradingstatus == ASSIGN_GRADING_STATUS_NOT_GRADED) {
$statusstr = get_string($status->gradingstatus, 'assign');
} else {
$gradingstatus = 'markingworkflowstate' . $status->gradingstatus;
$statusstr = get_string($gradingstatus, 'assign');
}
if ($status->gradingstatus == ASSIGN_GRADING_STATUS_GRADED ||
$status->gradingstatus == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
$classname = 'submissiongraded';
} else {
$classname = 'submissionnotgraded';
}
$o .= $this->output->container($statusstr, $classname);
$submission = $status->teamsubmission ? $status->teamsubmission : $status->submission;
$duedate = $status->duedate;
if ($duedate > 0) {
if ($status->extensionduedate) {
// Extension date.
$duedate = $status->extensionduedate;
}
// Time remaining.
$classname = 'timeremaining';
if ($duedate - $time <= 0) {
if (!$submission ||
$submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
if ($status->submissionsenabled) {
$remaining = get_string('overdue', 'assign', format_time($time - $duedate));
$classname = 'overdue';
} else {
$remaining = get_string('duedatereached', 'assign');
}
} else {
if ($submission->timemodified > $duedate) {
$remaining = get_string('submittedlate',
'assign',
format_time($submission->timemodified - $duedate));
$classname = 'latesubmission';
} else {
$remaining = get_string('submittedearly',
'assign',
format_time($submission->timemodified - $duedate));
$classname = 'earlysubmission';
}
}
} else {
$remaining = get_string('paramtimeremaining', 'assign', format_time($duedate - $time));
}
$o .= $this->output->container($remaining, $classname);
}
// Show graders whether this submission is editable by students.
if ($status->view == assign_submission_status::GRADER_VIEW) {
if ($status->canedit) {
$o .= $this->output->container(get_string('submissioneditable', 'assign'), 'submissioneditable');
} else {
$o .= $this->output->container(get_string('submissionnoteditable', 'assign'), 'submissionnoteditable');
}
}
// Grading criteria preview.
if (!empty($status->gradingcontrollerpreview)) {
$o .= $this->output->container($status->gradingcontrollerpreview, 'gradingmethodpreview');
}
if ($submission) {
if (!$status->teamsubmission || $status->submissiongroup != false || !$status->preventsubmissionnotingroup) {
foreach ($status->submissionplugins as $plugin) {
$pluginshowsummary = !$plugin->is_empty($submission) || !$plugin->allow_submissions();
if ($plugin->is_enabled() &&
$plugin->is_visible() &&
$plugin->has_user_summary() &&
$pluginshowsummary
) {
$displaymode = assign_submission_plugin_submission::SUMMARY;
$pluginsubmission = new assign_submission_plugin_submission($plugin,
$submission,
$displaymode,
$status->coursemoduleid,
$status->returnaction,
$status->returnparams);
$plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
$o .= $this->output->container($this->render($pluginsubmission), 'assignsubmission ' . $plugincomponent);
}
}
}
}
$o .= $this->output->container_end();
return $o;
}
/** /**
* Render a table containing the current status of the submission. * Render a table containing the current status of the submission.
* *
@ -772,6 +962,21 @@ class mod_assign_renderer extends plugin_renderer_base {
return $o; return $o;
} }
/**
* Output the attempt history chooser for this assignment
*
* @param assign_attempt_history_chooser $history
* @return string
*/
public function render_assign_attempt_history_chooser(assign_attempt_history_chooser $history) {
$o = '';
$context = $history->export_for_template($this);
$o .= $this->render_from_template('mod_assign/attempt_history_chooser', $context);
return $o;
}
/** /**
* Output the attempt history for this assignment * Output the attempt history for this assignment
* *
@ -797,7 +1002,7 @@ class mod_assign_renderer extends plugin_renderer_base {
} }
$containerid = 'attempthistory' . uniqid(); $containerid = 'attempthistory' . uniqid();
$o .= $this->heading(get_string('attempthistory', 'assign'), 3); $o .= $this->output->heading(get_string('attempthistory', 'assign'), 3);
$o .= $this->box_start('attempthistory', $containerid); $o .= $this->box_start('attempthistory', $containerid);
foreach ($history->submissions as $i => $submission) { foreach ($history->submissions as $i => $submission) {
@ -1248,5 +1453,14 @@ class mod_assign_renderer extends plugin_renderer_base {
return $o; return $o;
} }
/**
* Defer to template..
*
* @param grading_app - All the data to render the grading app.
*/
public function render_grading_app(grading_app $app) {
$context = $app->export_for_template($this);
return $this->render_from_template('mod_assign/grading_app', $context);
}
} }

View file

@ -308,3 +308,697 @@
.path-mod-assign .editsubmissionform label[for="id_submissionstatement"] { .path-mod-assign .editsubmissionform label[for="id_submissionstatement"] {
display: inline-block; display: inline-block;
} }
.path-mod-assign.layout-option-nonavbar {
padding-top: 0px;
}
.path-mod-assign [data-region="user-selector"] select {
margin-bottom: 0px;
}
.path-mod-assign [data-region="user-selector"] .alignment {
float: right;
width: 320px;
text-align: center;
margin-top: 7px;
}
.path-mod-assign [data-region="user-selector"] [data-action="previous-user"],
.path-mod-assign [data-region="user-selector"] [data-action="next-user"] {
font-size: 26px;
}
.path-mod-assign [data-region="user-selector"] [data-action="next-user"] {
margin-left: -10px;
}
.dir-rtl.path-mod-assign [data-region="user-selector"] [data-action="next-user"] {
margin-right: -10px;
}
.dir-rtl.path-mod-assign [data-region="user-selector"] .alignment {
float: left;
}
.path-mod-assign [data-region="user-selector"] .alignment input {
margin-bottom: 5px;
}
.path-mod-assign [data-region="user-selector"] .alignment .form-autocomplete-downarrow {
top: 0;
}
.path-mod-assign [data-region="user-selector"] .form-autocomplete-selection {
display: none;
}
.path-mod-assign [data-region="user-selector"] .form-autocomplete-suggestions {
text-align: left;
}
.path-mod-assign [data-region="user-selector"] .form-autocomplete-suggestions {
margin-left: 48px;
}
.dir-rtl.path-mod-assign [data-region="user-selector"] .form-autocomplete-suggestions {
margin-right: 64px;
}
.path-mod-assign [data-region="user-filters"] {
font-size: small;
}
.path-mod-assign [data-region="configure-filters"] {
display: none;
text-align: left;
width: auto;
background-color: #fff;
background-clip: padding-box;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
border-radius: 6px;
position: absolute;
margin-top: 28px;
margin-left: -140px;
padding: 10px 0;
z-index: 1;
}
.path-mod-assign [data-region="configure-filters"]::before,
.path-mod-assign [data-region="configure-filters"]::after {
position: absolute;
left: auto;
display: inline-block;
content: '';
border-style: solid;
border-color: transparent;
border-top: none;
}
.path-mod-assign [data-region="configure-filters"]::before {
top: -7px;
right: 12px;
border-width: 7px;
border-bottom-color: rgba(0, 0, 0, 0.2);
}
.path-mod-assign [data-region="configure-filters"]::after {
top: -6px;
right: 13px;
border-width: 6px;
border-bottom-color: #fff;
}
.path-mod-assign.dir-rtl [data-region="configure-filters"] {
text-align: right;
margin-left: 0;
margin-right: -140px;
}
.path-mod-assign [data-region="configure-filters"] label {
padding: 3px 20px;
}
.path-mod-assign .alignment [data-region="configure-filters"] input {
margin-bottom: 0;
}
.path-mod-assign [data-region="grading-navigation-panel"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 6em;
margin: 0;
border-bottom: 1px solid #ddd;
}
.path-mod-assign [data-region="grading-navigation"] {
padding: 1em;
}
.path-mod-assign [data-region="user-info"] {
height: 60px;
}
.path-mod-assign [data-region="user-info"] a {
text-decoration: none;
}
.path-mod-assign [data-region="user-info"] .img-rounded {
display: block;
float: left;
margin-top: -3px;
margin-right: 10px;
}
.dir-rtl.path-mod-assign [data-region="user-info"] .img-rounded {
float: right;
margin-left: 10px;
}
.path-mod-assign [data-region="user-info"] em {
display: block;
font-style: normal;
}
.path-mod-assign [data-region="grading-actions-form"] label {
display: inline-block;
}
.path-mod-assign.pagelayout-embedded {
overflow: hidden;
}
.path-mod-assign [data-region="review-panel"] {
position: absolute;
top: 85px;
bottom: 60px;
left: 0;
width: 70%;
box-sizing: border-box;
}
.path-mod-assign [data-region="review-panel"] .pageheader {
border-right: 1px solid #ddd;
}
.path-mod-assign [data-region="review-panel"] .drawingregion {
left: 0;
right: 0;
border-color: #ddd;
}
.path-mod-assign [data-region="grade-panel"].fullwidth {
position: absolute;
top: 7em;
left: 0;
right: 0;
width: 99%;
overflow: auto;
bottom: 7em;
}
.path-mod-assign [data-region="grade-panel"].fullwidth [data-region="grade"] {
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.path-mod-assign [data-region="grade-panel"] {
position: absolute;
top: 85px;
bottom: 60px;
right: 0;
width: 30%;
overflow: auto;
box-sizing: border-box;
background-color: #f5f5f5;
padding: 15px;
padding-top: 0px;
}
.path-mod-assign [data-region="grade-panel"] h3 {
font-size: 18px;
font-weight: 500;
}
/***** Start submission status *****/
.path-mod-assign [data-region="grade-panel"] div.submissionstatustable {
margin-bottom: 2em;
}
.path-mod-assign [data-region="grade-panel"] .submissionsummarytable {
margin-left: 5px;
margin-right: 5px;
}
.path-mod-assign [data-region="grade-panel"] .submissionsummarytable table.generaltable td {
padding: 8px 0;
background-color: transparent;
}
.path-mod-assign [data-region="grade-panel"] .submissionsummarytable .generaltable tbody > tr:nth-child(2n+1) > td,
.path-mod-assign [data-region="grade-panel"] .submissionsummarytable .generaltable tbody tr:hover > td {
background-color: transparent;
}
.path-mod-assign [data-region="grade-panel"] div.submissionsummarytable table tbody tr td.c0 {
width: auto;
}
.path-mod-assign [data-region="grade-panel"] div.submissionsummarytable table tbody tr.lastrow td.c0,
.path-mod-assign [data-region="grade-panel"] div.submissionsummarytable table tbody tr.lastrow td.c1 {
border-bottom: 1px solid #ddd;
}
.path-mod-assign [data-region="grade-panel"] td.submissionnotgraded,
.path-mod-assign [data-region="grade-panel"] div.submissionnotgraded {
color: red;
background-color: transparent;
}
/***** End submission status *****/
.path-mod-assign [data-region="grade-panel"] #id_gradeheader {
display: table-cell;
}
.path-mod-assign [data-region="grade-panel"] #id_gradeheader > legend {
visibility: hidden;
height: 0;
margin-bottom: 0;
}
.path-mod-assign [data-region="grade-panel"] .comment-area textarea[cols] {
width: 100%;
box-sizing: border-box;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_ftext,
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_f,
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_feditor,
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_ffilemanager {
background-color: #fff;
border: 1px solid #ddd;
margin-bottom: 20px;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_ftext .fitemtitle,
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_f .fitemtitle,
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_feditor .fitemtitle,
.path-mod-assign [data-region="grade-panel"] .mform .fitem.fitem_ffilemanager .fitemtitle {
padding-left: 5px;
padding-right: 5px;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.fitem_ftext .felement,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.fitem_f .felement,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.fitem_feditor .felement,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.fitem_ffilemanager .felement {
padding: 6px 10px 10px;
box-sizing: border-box;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fitem.fitem_ftext .fitemtitle,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fitem.fitem_f .fitemtitle,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fitem.fitem_feditor .fitemtitle,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fitem.fitem_ffilemanager .fitemtitle {
border-bottom: 1px solid #ddd;
box-shadow: 0 1px 1px rgba(0,0,0,0.05);
padding: 6px 10px 3px;
box-sizing: border-box;
}
.path-mod-assign #page-content [data-region="grade-panel"] [data-region="popout-button"] img {
margin-left: 2px;
margin-right: 2px;
margin-top: -2px;
}
.path-mod-assign #page-content [data-region="grade-panel"] .popout [data-region="popout-button"] img {
margin-left: -6px;
margin-right: -6px;
margin-top: 4px;
}
.path-mod-assign [data-region="grade-panel"] .fitem .fstaticlabel,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fitem .fitemtitle label {
font-weight: 500;
}
/***** Start grade *****/
.path-mod-assign [data-region="grade-panel"] .mform #fitem_id_grade.fitem {
padding-top: 5px;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) #fitem_id_grade.fitem .fitemtitle {
display: inline-block;
width: auto;
border-bottom: none;
box-shadow: none;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) #fitem_id_grade.fitem .felement {
width: auto;
float: right;
}
.path-mod-assign #page-content .mform:not(.unresponsive) #fitem_id_grade.fitem .felement input {
width: 80px;
margin-bottom: 0;
}
/***** End grade *****/
/***** Start rubric *****/
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric {
padding-bottom: 0;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion .description {
font-weight: 500;
min-width: 150px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion .levels {
background-color: #fff;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion,
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion.even {
background-color: transparent;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric.evaluate .criterion .levels .level:hover {
background-color: #dff0d8;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion .levels .level.checked {
background-color: #dff0d8;
border: none;
border-left: 1px solid #ddd;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion .levels .level .score {
color: #468847;
font-weight: 500;
font-style: normal;
margin-top: 20px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion .remark textarea {
margin-bottom: 0;
}
/***** End rubric *****/
/***** Start marking guide *****/
.path-mod-assign [data-region="grade-panel"] .gradingform_guide {
margin-bottom: 10px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .descriptionreadonly,
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .remark,
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .score {
display: block;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .descriptionreadonly {
padding-top: 10px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .criteriondescription {
margin-top: 5px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .criteriondescriptionmarkers {
width: auto;
margin-top: 5px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .markingguideremark {
margin-bottom: 10px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .remark .commentchooser {
float: right;
margin-top: 2px;
margin-left: 0;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .score {
float: left;
padding-bottom: 8px;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .score input,
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .score div {
display: inline-block;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .criterion,
.path-mod-assign [data-region="grade-panel"] .gradingform_guide .criterion.even {
background-color: transparent;
border-width: 0 0 1px 0;
padding: 8px 0;
}
.path-mod-assign [data-region="grade-panel"] .showmarkerdesc,
.path-mod-assign [data-region="grade-panel"] .showstudentdesc {
background-color: #f5f5f5;
padding: 10px;
}
/***** End marking guide *****/
.path-mod-assign [data-region="grade-panel"] .fitem.fitem_ffilemanager {
margin-bottom: 0;
}
/***** Start popout dialogue *****/
.path-mod-assign [data-region="grade-panel"] .fitem.popout {
position: fixed;
left: 20%;
right: 20%;
top: 20%;
z-index: 1000;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 6px;
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.popout .fitemtitle {
text-align: center;
padding-left: 15px;
padding-right: 15px;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.popout .fitemtitle label {
font-size: 16px;
line-height: 30px;
}
.path-mod-assign #page-content [data-region="grade-panel"] [data-region="popout-button"] {
float: right;
}
.dir-rtl.path-mod-assign #page-content [data-region="grade-panel"] [data-region="popout-button"] {
float: left;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fitem.popout .fitemtitle [data-region="popout-button"] img {
margin-top: -10px;
margin-right: -7px;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.popout .felement {
padding: 10px 15px 15px;
}
/***** End popout dialogue *****/
/***** Start attempt settings *****/
.path-mod-assign [data-region="grade-panel"] #id_attemptsettings > legend {
font-size: 18px;
font-weight: 500;
line-height: 40px;
border-bottom: 0;
margin-bottom: 10px;
}
.path-mod-assign [data-region="grade-panel"] #id_attemptsettings .fcontainer {
display: table;
width: 100%;
padding-left: 5px;
padding-right: 5px;
margin-bottom: 10px;
box-sizing: border-box;
}
.path-mod-assign [data-region="grade-panel"] .mform #id_attemptsettings .fitem {
display: table-row;
margin-bottom: 0;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) #id_attemptsettings .fitem .fitemtitle,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) #id_attemptsettings .fitem .felement {
display: table-cell;
float: none;
border-top: 1px solid #ddd;
padding: 8px 0;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) #id_attemptsettings .fitem:last-of-type .fitemtitle,
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) #id_attemptsettings .fitem:last-of-type .felement {
border-bottom: 1px solid #ddd;
}
.path-mod-assign [data-region="grade-panel"] #id_attemptsettings .fitem .fstaticlabel,
.path-mod-assign [data-region="grade-panel"] .mform:not(.unresponsive) #id_attemptsettings .fitem .fitemtitle label {
font-weight: 400;
}
.path-mod-assign [data-region="grade-panel"] .mform:not(.unresponsive) #id_attemptsettings .fitem .felement select {
margin-bottom: 0;
}
.path-mod-assign [data-region="grade-panel"] [data-region="attempt-chooser"] {
margin-bottom: 10px;
vertical-align: text-bottom;
}
/***** End attempt settings *****/
.path-mod-assign [data-region="grade-actions-panel"] {
border-top: 1px solid #ddd;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
}
.path-mod-assign [data-region="grade-actions"] {
padding: 1em;
text-align: center;
}
.path-mod-assign [data-region="submissions-list"] {
text-align: initial;
}
.path-mod-assign [data-region="submissions-list"] label.radio input {
margin-top: 4px;
min-width: inherit;
}
.path-mod-assign [data-region="overlay"] {
display: none;
z-index: 100;
position: absolute;
top: 0em;
left: 0;
width: 100%;
overflow: auto;
bottom: 0em;
background-color: #ddd;
opacity: 0.4;
padding-top: 4em;
text-align: center;
}
@media (max-width: 767px) {
.path-mod-assign.pagelayout-embedded {
overflow: auto;
}
.path-mod-assign [data-region="assignment-info"] {
border-bottom: 1px solid #ddd;
padding-bottom: 5px;
}
.path-mod-assign .page-context-header .page-header-headings {
margin-top: 13px;
}
.path-mod-assign [data-region="grading-navigation-panel"],
.path-mod-assign [data-region="review-panel"],
.path-mod-assign [data-region="grade-panel"],
.path-mod-assign [data-region="grade-actions-panel"] {
position: inherit;
width: 100%;
top: 0;
left: 0;
overflow: none;
height: auto;
margin-bottom: 1em;
}
.path-mod-assign [data-region="grading-navigation"] {
padding: 0;
text-align: center;
}
.path-mod-assign [data-region="grade-panel"] {
margin-bottom: 2em;
}
.path-mod-assign [data-region="grade-panel"] [data-region="popout-button"] {
display: none;
}
.path-mod-assign [data-region="review-panel"] .pageheader {
border-right: none;
}
.path-mod-assign.pagelayout-popup {
overflow: inherit;
}
.path-mod-assign [data-region="grading-navigation"] [data-region="user-info"] {
text-align: left;
width: auto;
display: inline-block;
margin: 0 auto;
}
.path-mod-assign [data-region="user-selector"] .alignment {
float: none;
margin: 0 auto 10px;
}
}
/** Start of CSS to make forms vertical in the grading panel (taken from theme/bootstrapbase/less/moodle/forms.less). */
.path-mod-assign [data-region="grade-panel"] .mform .fitem .fitemtitle {
display: block;
margin-top: 4px;
margin-bottom: 4px;
text-align: left;
width: 100%;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem .felement {
margin-left: 0;
width: 100%;
float: left;
padding-left: 0;
padding-right: 0;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem .fstatic:empty {
display: none;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem .fcheckbox > span,
.path-mod-assign [data-region="grade-panel"] .mform .fitem .fradio > span,
.path-mod-assign [data-region="grade-panel"] .mform .fitem .fgroup > span,
margin-top: 4px;
}
.path-mod-assign [data-region="grade-panel"] .mform .femptylabel .fitemtitle {
display: inline-block;
width: auto;
margin-right: 8px;
}
.path-mod-assign [data-region="grade-panel"] .mform .femptylabel .felement {
display: inline-block;
margin-top: 4px;
padding-top: 5px;
width: auto;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem_fcheckbox .fitemtitle,
.path-mod-assign [data-region="grade-panel"] .mform .fitem_fcheckbox .felement {
display: inline-block;
width: auto;
}
.path-mod-assign [data-region="grade-panel"] .mform .fitem_fcheckbox .felement {
padding: 6px;
}
.dir-rtl.path-mod-assign [data-region="grade-panel"] .mform .femptylabel .fitemtitle {
margin-right: 0px;
margin-left: 8px;
}
.dir-rtl.path-mod-assign [data-region="grade-panel"] .mform .fitem .fitemtitle {
text-align: right;
}
.dir-rtl.path-mod-assign [data-region="grade-panel"] .mform .fitem .felement {
margin-right: 0;
float: right;
padding-right: 0;
padding-left: 0;
}
.dir-rtl.path-mod-assign [data-region="grade-panel"] .mform .fitem_checkbox .felement {
float: right;
}
/** End of CSS to make forms vertical in the grading panel (taken from theme/bootstrapbase/less/moodle/forms.less). */
/** Styles to fix base theme **/
.path-mod-assign #page,
.path-mod-assign #page-content {
position: initial;
}
/** End of base fixes **/

View file

@ -0,0 +1,16 @@
{{#submissioncount}}
<div>
<a class="btn" href="#" data-region="attempt-chooser" data-submissions="submissions-list-{{uniqid}}">{{#str}}viewadifferentattempt, mod_assign{{/str}}</a>
<div class="hide" id="submissions-list-{{uniqid}}">
<div data-region="submissions-list">
{{#submissions}}
<label class="radio">
<input type="radio" name="select-attemptnumber" value="{{attemptnumber}}"/>
{{attemptsummary}}
<br><em>{{statussummary}}</em>
</label>
{{/submissions}}
</div>
</div>
</div>
{{/submissioncount}}

View file

@ -0,0 +1,46 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_actions
Actions panel at the bottom of the assignment grading UI.
Classes required for JS:
* none
Data attributes required for JS:
* data-region
Context variables required for this template:
* see mod/assign/classes/output/grading_app.php
Example context (json):
This template is initially hidden, and is only displayed after the current user info has been loaded.
}}
<form data-region="grading-actions-form" class="hide">
<label>{{#str}}sendstudentnotifications, mod_assign{{/str}}
<input type="checkbox" name="sendstudentnotifications"
{{#defaultsendnotifications}}checked="checked"{{/defaultsendnotifications}} />
</label>
<button type="submit" class="btn btn-primary" name="savechanges">{{#str}}savechanges{{/str}}</button>
<button type="submit" class="btn" name="resetbutton">{{#str}}reset{{/str}}</button>
</form>
{{#js}}
require(['mod_assign/grading_actions'], function(GradingActions) {
new GradingActions('[data-region="grade-actions"]');
});
{{/js}}

View file

@ -0,0 +1,56 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_app
Actions panel at the bottom of the assignment grading UI.
Classes required for JS:
* Uses some bootstrap classes, but for visual appeal only.
Data attributes required for JS:
* data-region, data-first-userid, data-courseid, data-contextid, data-assignmentid
Context variables required for this template:
* see mod/assign/classes/output/grading_app.php
Example context (json):
This template includes ajax functionality, so it cannot be shown in the template library.
}}
<div data-region="grading-navigation-panel" data-first-userid="{{userid}}" data-courseid="{{courseid}}" data-showuseridentity="{{showuseridentity}}">
{{> mod_assign/grading_navigation }}
</div>
<div data-region="grade-panel" {{^showreview}}class="fullwidth"{{/showreview}}>
<div data-region="grade" data-contextid="{{contextid}}" data-assignmentid="{{assignmentid}}">
{{> mod_assign/grading_panel }}
</div>
</div>
{{#showreview}}
<div data-region="review-panel">
<div data-region="review">
{{> mod_assign/review_panel }}
</div>
</div>
{{/showreview}}
<div data-region="grade-actions-panel">
<div data-region="grade-actions">
{{> mod_assign/grading_actions }}
</div>
</div>
<div data-region="overlay" class="moodle-has-zindex">
{{> mod_assign/grading_save_in_progress }}
</div>

View file

@ -0,0 +1,73 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_navigation
Actions panel at the bottom of the assignment grading UI.
Classes required for JS:
* none
Data attributes required for JS:
* data-region, data-assignmentid, data-groupid
Context variables required for this template:
* see mod/assign/classes/output/grading_app.php
Example context (json):
This template includes ajax functionality, so it cannot be shown in the template library.
}}
<div data-region="grading-navigation" class="row-fluid">
<div data-region="assignment-info" class="span4">
<a href="{{config.wwwroot}}/course/view.php?id={{courseid}}">{{coursename}}</a><br/>
<a href="{{config.wwwroot}}/mod/assign/view.php?id={{cmid}}">{{name}}</a>
{{#caneditsettings}}
<a href="{{config.wwwroot}}/course/modedit.php?update={{cmid}}&return=1">{{#pix}}t/edit, core,{{#str}}editsettings{{/str}}{{/pix}}</a>
{{/caneditsettings}}
<div role="tooltip" id="tooltip-{{uniqid}}" class="accesshide">
{{#duedate}}
{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}}
{{/duedate}}
{{#cutoffdate}}
<br>{{cutoffdatestr}}
{{/cutoffdate}}
{{#duedate}}
<br>{{timeremainingstr}}
{{/duedate}}
</div>
{{#duedate}}
<br/><small data-region="assignment-tooltip" aria-describedby="tooltip-{{uniqid}}">{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}}</small>
{{/duedate}}
</span>
</div>
<div data-region="user-info" class="span4" data-assignmentid="{{assignmentid}}" data-groupid="{{groupid}}">
{{> mod_assign/grading_navigation_user_info }}
</div>
<div data-region="user-selector" class="span4">
<div class="alignment">
{{> mod_assign/grading_navigation_user_selector }}
</div>
</div>
</div>
{{#js}}
require(['mod_assign/grading_navigation', 'core/tooltip'], function(GradingNavigation, ToolTip) {
var nav = new GradingNavigation('[data-region="user-selector"]');
var tooltip = new ToolTip('[data-region="assignment-tooltip"]');
});
{{/js}}

View file

@ -0,0 +1,34 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_navigation_no_users
Notification when no users are available (given the current filters).
Classes required for JS:
* none
Data attributes required for JS:
* None
Context variables required for this template:
* None
Example context (json):
{ }
}}
<h3>{{#str}}nousersselected, mod_assign{{/str}}</h3>

View file

@ -0,0 +1,40 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_navigation_user_info
Initial template for the user info panel - until a user is selected
a loading spinner will be displayed.
Classes required for JS:
* none
Data attributes required for JS:
* None
Context variables required for this template:
* None
Example context (json):
{ }
}}
{{> mod_assign/loading }}
{{#js}}
require(['mod_assign/grading_navigation_user_info'], function(UserInfo) {
new UserInfo('[data-region="user-info"]');
});
{{/js}}

View file

@ -0,0 +1,67 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_navigation_user_selector
The template HTML for the user selector in the top right corner.
Classes required for JS:
* none
Data attributes required for JS:
* data-action, data-assignmentid, data-groupid, data-region
Context variables required for this template:
* see mod/assign/classes/output/grading_app.php
Example context (json):
This template uses ajax functionality, so it cannot be shown in the template library.
}}
<a href="#previous" data-action="previous-user">{{larrow}}</a>
<span>
<select data-action="change-user" data-assignmentid="{{assignmentid}}" data-groupid="{{groupid}}">
</select>
</span>
<a href="#next" data-action="next-user">{{rarrow}}</a>
<br>
<span data-region="user-count">
<small>
<span data-region="user-count-summary">{{#str}}xofy, mod_assign, { "x": "{{index}}", "y": "{{count}}" }{{/str}}</span>
</small>
</span>
<span data-region="configure-filters" id="filter-configuration-{{uniqid}}" class="well well-small">
<form>
<label><input type="checkbox" name="filter_submitted">{{#str}}filtersubmitted, mod_assign{{/str}}</label>
<label><input type="checkbox" name="filter_notsubmitted">{{#str}}filternotsubmitted, mod_assign{{/str}}</label>
<label><input type="checkbox" name="filter_requiregrading">{{#str}}filterrequiregrading, mod_assign{{/str}}</label>
</form>
</span>
<a href="#" data-region="user-filters" title="{{#str}}changefilters, mod_assign{{/str}}" aria-expanded="false" aria-controls="filter-configuration-{{uniqid}}">
<span class="accesshide">
{{#filters}}
{{filtername}}
{{/filters}}
{{^filters}}
{{#str}}nofilters, mod_assign{{/str}}
{{/filters}}
</span>
{{#pix}}i/filter{{/pix}}
</a>

View file

@ -0,0 +1,40 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_navigation_user_summary
Shown when the real data for a user has been loaded. Display in the top left corner.
Classes required for JS:
* none
Data attributes required for JS:
* data-region, data-assignmentid, data-groupid
Context variables required for this template:
* id, courseid, all fields from mod_assign_list_participants
Example context (json):
{
"id": "5",
"fullname": "Mr T",
"hasidentity": true,
"identity": "t@example.org, T"
"profileimageurl": "https://moodle.org/pix/u/f3.png"
}
}}
<h4><a href="{{globals.config.wwwroot}}/user/view.php?id={{id}}&course={{courseid}}">{{#profileimageurl}}<img src="{{profileimageurl}}" class="img-rounded" height="40" role="presentation"/>{{/profileimageurl}} {{fullname}}{{#hasidentity}}<em><small> {{identity}} </small></em>{{/hasidentity}}</a></h4>

View file

@ -0,0 +1,39 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_panel
Shown a place holder until the real content is fetched via ajax.
Classes required for JS:
* none
Data attributes required for JS:
* None
Context variables required for this template:
* None
Example context (json):
This template loads it's content via ajax, so it cannot be shown in the template library.
}}
{{> mod_assign/loading }}
{{#js}}
require(['mod_assign/grading_panel'], function(GradingPanel) {
new GradingPanel('[data-region="grade"]');
});
{{/js}}

View file

@ -0,0 +1,35 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/grading_save_in_progress
Shown in an overlay when the grading form has been submitted, and we are
waiting for a response.
Classes required for JS:
* none
Data attributes required for JS:
* None
Context variables required for this template:
* None
Example context (json):
{ "id": "4" }
}}
<h3>{{#str}}savingchanges, mod_assign{{/str}} {{> mod_assign/loading }}</h3>

View file

@ -0,0 +1,41 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/list_participant_user_summary
Shown as the options for the list of users in the user picker on the grading page.
Classes required for JS:
* none
Data attributes required for JS:
* None
Context variables required for this template:
* fullname
* requiregrading
* identity - list of only the visible identity fields
* list of fields from mod_assign_list_participants webservice
Example context (json):
{ "id": "4",
"fullname": "Mr T",
"requiregrading": "true",
"identity": "email, phone, etc"
}
}}
<span>{{fullname}} <small>{{identity}}</small> {{#requiregrading}}<abbr title="{{#str}}filterrequiregrading, mod_assign{{/str}}">*</abbr>{{/requiregrading}}</span>

View file

@ -0,0 +1,34 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/loading
Loading spinner shown while waiting for an ajax request
Classes required for JS:
* None
Data attibutes required for JS:
* None
Context variables required for this template:
* None
Example context (json):
{ }
}}
{{#pix}}y/loading, core, {{#str}}loading, mod_assign{{/str}}{{/pix}}

View file

@ -0,0 +1 @@
<a href="#" data-region="popout-button">{{#pix}}e/fullscreen,core,{{#str}}togglezoom, mod_assign{{/str}}{{/pix}}</a>

View file

@ -0,0 +1,34 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_assign/review_panel
Show a placeholder in the review panel until something injects some content.
Classes required for JS:
* None
Data attibutes required for JS:
* None
Context variables required for this template:
* None
Example context (json):
{ }
}}
{{> mod_assign/loading }}

View file

@ -310,11 +310,15 @@ class testable_assign extends assign {
} }
public function testable_view_batch_set_workflow_state($selectedusers) { public function testable_view_batch_set_workflow_state($selectedusers) {
global $PAGE;
$PAGE->set_url('/mod/assign/view.php');
$mform = $this->testable_grading_batch_operations_form('setmarkingworkflowstate', $selectedusers); $mform = $this->testable_grading_batch_operations_form('setmarkingworkflowstate', $selectedusers);
return parent::view_batch_set_workflow_state($mform); return parent::view_batch_set_workflow_state($mform);
} }
public function testable_view_batch_markingallocation($selectedusers) { public function testable_view_batch_markingallocation($selectedusers) {
global $PAGE;
$PAGE->set_url('/mod/assign/view.php');
$mform = $this->testable_grading_batch_operations_form('setmarkingallocation', $selectedusers); $mform = $this->testable_grading_batch_operations_form('setmarkingallocation', $selectedusers);
return parent::view_batch_markingallocation($mform); return parent::view_batch_markingallocation($mform);
} }

View file

@ -38,11 +38,13 @@ Feature: In an assignment, students start a new attempt based on their previous
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Allow another attempt | 1 | | Allow another attempt | 1 |
And I press "Save changes" And I press "Save changes"
And I press "Ok"
And I click on "Edit settings" "link"
And I log out And I log out
And I log in as "student1" And I log in as "student1"
And I follow "Course 1" And I follow "Course 1"
@ -53,8 +55,8 @@ Feature: In an assignment, students start a new attempt based on their previous
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I should see "I'm the student first submission" And I should see "I'm the student first submission"
@javascript @_alert @javascript @_alert
@ -110,7 +112,7 @@ Feature: In an assignment, students start a new attempt based on their previous
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
Then "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading" Then "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading"
And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading" And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading"
And "Student 3" row "Status" column of "generaltable" table should contain "No submission" And "Student 3" row "Status" column of "generaltable" table should contain "No submission"
@ -140,12 +142,12 @@ Feature: In an assignment, students start a new attempt based on their previous
# And I log in as "teacher1" # And I log in as "teacher1"
# And I follow "Course 1" # And I follow "Course 1"
# And I follow "Test assignment name" # And I follow "Test assignment name"
# And I follow "View/grade all submissions" # And I follow "View all submissions"
# And "Student 1" row "Status" column of "generaltable" table should contain "Reopened" # And "Student 1" row "Status" column of "generaltable" table should contain "Reopened"
# And "Student 2" row "Status" column of "generaltable" table should contain "Reopened" # And "Student 2" row "Status" column of "generaltable" table should contain "Reopened"
# And "Student 3" row "Status" column of "generaltable" table should contain "Submitted for grading" # And "Student 3" row "Status" column of "generaltable" table should contain "Submitted for grading"
# And "Student 4" row "Status" column of "generaltable" table should contain "Submitted for grading" # And "Student 4" row "Status" column of "generaltable" table should contain "Submitted for grading"
# And I click on "Grade Student 3" "link" in the "Student 3" "table_row" # And I click on "Grade " "link" in the "Student 3" "table_row"
# And I set the following fields to these values: # And I set the following fields to these values:
# | Allow another attempt | 1 | # | Allow another attempt | 1 |
# And I press "Save changes" # And I press "Save changes"
@ -162,6 +164,6 @@ Feature: In an assignment, students start a new attempt based on their previous
# And I log in as "teacher1" # And I log in as "teacher1"
# And I follow "Course 1" # And I follow "Course 1"
# And I follow "Test assignment name" # And I follow "Test assignment name"
# And I follow "View/grade all submissions" # And I follow "View all submissions"
# And I click on "Grade Student 4" "link" in the "Student 1" "table_row" # And I click on "Grade" "link" in the "Student 1" "table_row"
#And I should see "This is attempt 2 (3 attempts allowed)" #And I should see "This is attempt 2 (3 attempts allowed)"

View file

@ -40,14 +40,17 @@ Feature: In an assignment, teachers can edit a students submission inline
When I log in as "teacher1" When I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Grade out of 100 | 50 | | Grade out of 100 | 50 |
| Feedback comments | I'm the teacher feedback | | Feedback comments | I'm the teacher feedback |
And I upload "lib/tests/fixtures/empty.txt" file to "Feedback files" filemanager And I upload "lib/tests/fixtures/empty.txt" file to "Feedback files" filemanager
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
Then I should see "50.00" in the "Student 1" "table_row" Then I should see "50.00" in the "Student 1" "table_row"
And I should see "Submitted for grading" in the "Student 1" "table_row" And I should see "Submitted for grading" in the "Student 1" "table_row"
And I should see "Graded" in the "Student 1" "table_row" And I should see "Graded" in the "Student 1" "table_row"

View file

@ -28,8 +28,8 @@ Feature: Check that the assignment grade can not be input in a wrong format.
| Description | Test assignment description | | Description | Test assignment description |
| Use marking workflow | Yes | | Use marking workflow | Yes |
When I follow "Test assignment name" When I follow "Test assignment name"
Then I follow "View/grade all submissions" Then I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Grade out of 100" to "50,,6" And I set the field "Grade out of 100" to "50,,6"
And I press "Save changes" And I press "Save changes"
And I should see "The grade provided could not be understood: 50,,6" And I should see "The grade provided could not be understood: 50,,6"
@ -58,8 +58,8 @@ Feature: Check that the assignment grade can not be input in a wrong format.
| Description | Test assignment description | | Description | Test assignment description |
| Use marking workflow | Yes | | Use marking workflow | Yes |
When I follow "Test assignment name" When I follow "Test assignment name"
Then I follow "View/grade all submissions" Then I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Grade out of 100" to "50..6" And I set the field "Grade out of 100" to "50..6"
And I press "Save changes" And I press "Save changes"
And I should see "The grade provided could not be understood: 50..6" And I should see "The grade provided could not be understood: 50..6"

View file

@ -28,11 +28,14 @@ Feature: Check that the assignment grade can be updated correctly
| Description | Test assignment description | | Description | Test assignment description |
| Use marking workflow | Yes | | Use marking workflow | Yes |
When I follow "Test assignment name" When I follow "Test assignment name"
Then I follow "View/grade all submissions" Then I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Grade out of 100" to "50" And I set the field "Grade out of 100" to "50"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And "Student 1" row "Grade" column of "generaltable" table should contain "50.00" And "Student 1" row "Grade" column of "generaltable" table should contain "50.00"
@javascript @javascript
@ -61,9 +64,12 @@ Feature: Check that the assignment grade can be updated correctly
| Students submit in groups | Yes | | Students submit in groups | Yes |
| Group mode | No groups | | Group mode | No groups |
When I follow "Test assignment name" When I follow "Test assignment name"
Then I follow "View/grade all submissions" Then I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Grade out of 100" to "50" And I set the field "Grade out of 100" to "50"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And "Student 1" row "Grade" column of "generaltable" table should contain "50.00" And "Student 1" row "Grade" column of "generaltable" table should contain "50.00"

View file

@ -40,13 +40,15 @@ Feature: In an assignment, teachers can edit feedback for a students previous su
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 2" "link" in the "Student 2" "table_row" And I click on "Grade" "link" in the "Student 2" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Grade | 49 | | Grade | 49 |
| Feedback comments | I'm the teacher first feedback | | Feedback comments | I'm the teacher first feedback |
| Allow another attempt | Yes | | Allow another attempt | Yes |
And I press "Save changes" And I press "Save changes"
And I click on "Ok" "button"
And I click on "Edit settings" "link"
And I log out And I log out
And I log in as "student2" And I log in as "student2"
And I follow "Course 1" And I follow "Course 1"
@ -56,13 +58,17 @@ Feature: In an assignment, teachers can edit feedback for a students previous su
When I log in as "teacher1" When I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 2" "link" in the "Student 2" "table_row" And I click on "Grade" "link" in the "Student 2" "table_row"
And I click on ".mod-assign-history-link" "css_element" And I click on "View a different attempt" "link"
And I click on "//div[contains(concat(' ', normalize-space(@class), ' '), ' confirmation-dialogue ')]//input[@value='0']" "xpath_element"
And I click on "View" "button"
And I set the following fields to these values: And I set the following fields to these values:
| Grade | 50 | | Grade | 50 |
| Feedback comments | I'm the teacher second feedback | | Feedback comments | I'm the teacher second feedback |
And I press "Save changes" And I press "Save changes"
And I click on "Ok" "button"
And I click on "Edit settings" "link"
And I log out And I log out
Then I log in as "student2" Then I log in as "student2"
And I follow "Course 1" And I follow "Course 1"

View file

@ -32,15 +32,17 @@ Feature: In an assignment, teachers can filter displayed submissions by assigned
| Use marking workflow | Yes | | Use marking workflow | Yes |
| Use marking allocation | Yes | | Use marking allocation | Yes |
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "allocatedmarker" to "Marker 1" And I set the field "allocatedmarker" to "Marker 1"
And I click on "Save changes" "button" And I press "Save changes"
And I press "Ok"
And I click on "Edit settings" "link"
And I log out And I log out
When I log in as "teacher1" When I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I set the field "markerfilter" to "Marker 1" And I set the field "markerfilter" to "Marker 1"
Then I should see "Student 1" Then I should see "Student 1"
And I should not see "Student 2" And I should not see "Student 2"

View file

@ -50,14 +50,17 @@ Feature: View the grading status of an assignment
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Not marked" in the "Student 1" "table_row" And I should see "Not marked" in the "Student 1" "table_row"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Grade out of 100" to "50" And I set the field "Grade out of 100" to "50"
And I set the field "Marking workflow state" to "In review" And I set the field "Marking workflow state" to "In review"
And I set the field "Feedback comments" to "Great job! Lol, not really." And I set the field "Feedback comments" to "Great job! Lol, not really."
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "In review" in the "Student 1" "table_row" And I should see "In review" in the "Student 1" "table_row"
And I log out And I log out
# View the grading status as a student. # View the grading status as a student.
@ -74,12 +77,15 @@ Feature: View the grading status of an assignment
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "In review" in the "Student 1" "table_row" And I should see "In review" in the "Student 1" "table_row"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Marking workflow state" to "Released" And I set the field "Marking workflow state" to "Released"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Released" in the "Student 1" "table_row" And I should see "Released" in the "Student 1" "table_row"
And I log out And I log out
# View the grading status as a student. # View the grading status as a student.
@ -96,12 +102,15 @@ Feature: View the grading status of an assignment
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Released" in the "Student 1" "table_row" And I should see "Released" in the "Student 1" "table_row"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Marking workflow state" to "In marking" And I set the field "Marking workflow state" to "In marking"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "In marking" in the "Student 1" "table_row" And I should see "In marking" in the "Student 1" "table_row"
# The grade should also remain displayed as it's stored in the assign DB tables, but the final grade should be empty. # The grade should also remain displayed as it's stored in the assign DB tables, but the final grade should be empty.
And "Student 1" row "Grade" column of "generaltable" table should contain "50.00" And "Student 1" row "Grade" column of "generaltable" table should contain "50.00"
@ -137,13 +146,16 @@ Feature: View the grading status of an assignment
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should not see "Graded" in the "Student 1" "table_row" And I should not see "Graded" in the "Student 1" "table_row"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Grade out of 100" to "50" And I set the field "Grade out of 100" to "50"
And I set the field "Feedback comments" to "Great job! Lol, not really." And I set the field "Feedback comments" to "Great job! Lol, not really."
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Graded" in the "Student 1" "table_row" And I should see "Graded" in the "Student 1" "table_row"
And I log out And I log out
# View the grading status as a student. # View the grading status as a student.

View file

@ -35,7 +35,7 @@ Feature: Grant an extension to an offline student
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
And I click on "Edit" "link" in the "Student 1" "table_row" And I click on "Edit" "link" in the "Student 1" "table_row"
And I follow "Grant extension" And I follow "Grant extension"
And I should see "Student 1 (student1@example.com)" And I should see "Student 1 (student1@example.com)"
@ -56,7 +56,7 @@ Feature: Grant an extension to an offline student
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
And I set the field "selectall" to "1" And I set the field "selectall" to "1"
And I set the field "operation" to "Grant extension" And I set the field "operation" to "Grant extension"
And I click on "Go" "button" confirming the dialogue And I click on "Go" "button" confirming the dialogue

View file

@ -35,7 +35,7 @@ Feature: Group assignment submissions
| Students submit in groups | Yes | | Students submit in groups | Yes |
| Group mode | No groups | | Group mode | No groups |
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
Then "//tr[contains(., 'Student 0')][contains(., 'Default group')]" "xpath_element" should exist Then "//tr[contains(., 'Student 0')][contains(., 'Default group')]" "xpath_element" should exist
And "//tr[contains(., 'Student 1')][contains(., 'Default group')]" "xpath_element" should exist And "//tr[contains(., 'Student 1')][contains(., 'Default group')]" "xpath_element" should exist
And "//tr[contains(., 'Student 2')][contains(., 'Default group')]" "xpath_element" should exist And "//tr[contains(., 'Student 2')][contains(., 'Default group')]" "xpath_element" should exist
@ -54,7 +54,7 @@ Feature: Group assignment submissions
And I add "Student 1 (student1@example.com)" user to "Group 1" group members And I add "Student 1 (student1@example.com)" user to "Group 1" group members
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And "//tr[contains(., 'Student 0')][contains(., 'Group 1')]" "xpath_element" should exist And "//tr[contains(., 'Student 0')][contains(., 'Group 1')]" "xpath_element" should exist
And "//tr[contains(., 'Student 1')][contains(., 'Group 1')]" "xpath_element" should exist And "//tr[contains(., 'Student 1')][contains(., 'Group 1')]" "xpath_element" should exist
And I should not see "Student 2" And I should not see "Student 2"
@ -113,7 +113,7 @@ Feature: Group assignment submissions
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
Then "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading" Then "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading"
And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading" And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading"
And "Student 3" row "Status" column of "generaltable" table should not contain "Submitted for grading" And "Student 3" row "Status" column of "generaltable" table should not contain "Submitted for grading"
@ -130,7 +130,7 @@ Feature: Group assignment submissions
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading" And "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading"
And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading" And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading"
And "Student 3" row "Status" column of "generaltable" table should contain "Submitted for grading" And "Student 3" row "Status" column of "generaltable" table should contain "Submitted for grading"

View file

@ -62,12 +62,15 @@ Feature: Outcome grading
When I log in as "teacher1" When I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "img[alt='Grade Student 0']" "css_element" And I click on "Grade" "link" in the "Student 0" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Outcome Test: | Excellent | | Outcome Test: | Excellent |
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
Then I should see "Outcome Test: Excellent" in the "Student 0" "table_row" Then I should see "Outcome Test: Excellent" in the "Student 0" "table_row"
And I should not see "Outcome Test: Excellent" in the "Student 1" "table_row" And I should not see "Outcome Test: Excellent" in the "Student 1" "table_row"
@ -109,22 +112,28 @@ Feature: Outcome grading
When I log in as "teacher1" When I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "img[alt='Grade Student 0']" "css_element" And I click on "Grade" "link" in the "Student 0" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Outcome Test: | Excellent | | Outcome Test: | Excellent |
| Apply grades and feedback to entire group | Yes | | Apply grades and feedback to entire group | Yes |
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
Then I should see "Outcome Test: Excellent" in the "Student 0" "table_row" Then I should see "Outcome Test: Excellent" in the "Student 0" "table_row"
And I should see "Outcome Test: Excellent" in the "Student 1" "table_row" And I should see "Outcome Test: Excellent" in the "Student 1" "table_row"
And I should not see "Outcome Test: Excellent" in the "Student 2" "table_row" And I should not see "Outcome Test: Excellent" in the "Student 2" "table_row"
And I click on "img[alt='Grade Student 1']" "css_element" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Outcome Test: | Disappointing | | Outcome Test: | Disappointing |
| Apply grades and feedback to entire group | No | | Apply grades and feedback to entire group | No |
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Outcome Test: Excellent" in the "Student 0" "table_row" And I should see "Outcome Test: Excellent" in the "Student 0" "table_row"
And I should see "Outcome Test: Disappointing" in the "Student 1" "table_row" And I should see "Outcome Test: Disappointing" in the "Student 1" "table_row"
And I should not see "Outcome Test: Disappointing" in the "Student 0" "table_row" And I should not see "Outcome Test: Disappointing" in the "Student 0" "table_row"

View file

@ -45,7 +45,7 @@ Feature: Prevent or allow assignment submission changes
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
And I click on "Edit" "link" in the "Student 1" "table_row" And I click on "Edit" "link" in the "Student 1" "table_row"
And I follow "Prevent submission changes" And I follow "Prevent submission changes"
Then I should see "Submission changes not allowed" Then I should see "Submission changes not allowed"
@ -59,7 +59,7 @@ Feature: Prevent or allow assignment submission changes
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Edit" "link" in the "Student 1" "table_row" And I click on "Edit" "link" in the "Student 1" "table_row"
And I follow "Allow submission changes" And I follow "Allow submission changes"
And I should not see "Submission changes not allowed" And I should not see "Submission changes not allowed"
@ -98,7 +98,7 @@ Feature: Prevent or allow assignment submission changes
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
When I follow "View/grade all submissions" When I follow "View all submissions"
And I set the field "selectall" to "1" And I set the field "selectall" to "1"
And I click on "Go" "button" confirming the dialogue And I click on "Go" "button" confirming the dialogue
Then I should see "Submission changes not allowed" in the "Student 1" "table_row" Then I should see "Submission changes not allowed" in the "Student 1" "table_row"
@ -112,7 +112,7 @@ Feature: Prevent or allow assignment submission changes
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I set the field "selectall" to "1" And I set the field "selectall" to "1"
And I set the field "id_operation" to "Unlock submissions" And I set the field "id_operation" to "Unlock submissions"
And I click on "Go" "button" confirming the dialogue And I click on "Go" "button" confirming the dialogue

View file

@ -1,4 +1,4 @@
@mod @mod_assign @mod @mod_assign @javascript
Feature: In an assignment, teachers grade multiple students on one page Feature: In an assignment, teachers grade multiple students on one page
In order to quickly give students grades and feedback In order to quickly give students grades and feedback
As a teacher As a teacher
@ -38,14 +38,14 @@ Feature: In an assignment, teachers grade multiple students on one page
And I am on site homepage And I am on site homepage
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name" And I follow "Test assignment name"
Then I should see "1" in the "Needs grading" "table_row" Then I should see "1" in the "Needs grading" "table_row"
@javascript
Scenario: Grade multiple students on one page Scenario: Grade multiple students on one page
Given the following "courses" exist: Given the following "courses" exist:
| fullname | shortname | category | groupmode | | fullname | shortname | category | groupmode |
@ -109,14 +109,17 @@ Feature: In an assignment, teachers grade multiple students on one page
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Grade out of 100 | 50.0 | | Grade out of 100 | 50.0 |
| M8d skillZ! | 1337 | | M8d skillZ! | 1337 |
| Feedback comments | I'm the teacher first feedback | | Feedback comments | I'm the teacher first feedback |
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
Then I click on "Quick grading" "checkbox" Then I click on "Quick grading" "checkbox"
And I set the field "User grade" to "60.0" And I set the field "User grade" to "60.0"
And I press "Save all quick grading changes" And I press "Save all quick grading changes"
@ -144,7 +147,7 @@ Feature: In an assignment, teachers grade multiple students on one page
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Hide User picture" "link" And I click on "Hide User picture" "link"
And I click on "Hide Full name" "link" And I click on "Hide Full name" "link"
And I click on "Hide Email address" "link" And I click on "Hide Email address" "link"

View file

@ -41,7 +41,7 @@ Feature: Submissions are unlocked when a new attempt is given
And I am on site homepage And I am on site homepage
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Edit" "link" in the "Student 1" "table_row" And I click on "Edit" "link" in the "Student 1" "table_row"
And I follow "Prevent submission changes" And I follow "Prevent submission changes"
And I should see "Submission changes not allowed" And I should see "Submission changes not allowed"
@ -76,7 +76,7 @@ Feature: Submissions are unlocked when a new attempt is given
And I am on site homepage And I am on site homepage
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Edit" "link" in the "Student 1" "table_row" And I click on "Edit" "link" in the "Student 1" "table_row"
And I follow "Prevent submission changes" And I follow "Prevent submission changes"
And I should see "Submission changes not allowed" And I should see "Submission changes not allowed"

View file

@ -44,27 +44,36 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Not marked" in the "I'm the student's first submission" "table_row" And I should see "Not marked" in the "I'm the student's first submission" "table_row"
And I click on "Grade Participant " "link" in the "I'm the student's first submission" "table_row" And I click on "Grade" "link" in the "I'm the student's first submission" "table_row"
And I set the field "Grade out of 100" to "50" And I set the field "Grade out of 100" to "50"
And I set the field "Marking workflow state" to "In review" And I set the field "Marking workflow state" to "In review"
And I set the field "Feedback comments" to "Great job! Lol, not really." And I set the field "Feedback comments" to "Great job! Lol, not really."
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "In review" in the "I'm the student's first submission" "table_row" And I should see "In review" in the "I'm the student's first submission" "table_row"
@javascript @javascript
Scenario: Student identities are revealed after releasing the grades. Scenario: Student identities are revealed after releasing the grades.
When I click on "Grade Participant " "link" in the "I'm the student's first submission" "table_row" When I click on "Grade" "link" in the "I'm the student's first submission" "table_row"
And I set the field "Marking workflow state" to "Ready for release" And I set the field "Marking workflow state" to "Ready for release"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Ready for release" in the "I'm the student's first submission" "table_row" And I should see "Ready for release" in the "I'm the student's first submission" "table_row"
And I click on "Grade Participant " "link" in the "I'm the student's first submission" "table_row" And I click on "Grade" "link" in the "I'm the student's first submission" "table_row"
And I set the field "Marking workflow state" to "Released" And I set the field "Marking workflow state" to "Released"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Released" in the "I'm the student's first submission" "table_row" And I should see "Released" in the "I'm the student's first submission" "table_row"
And I set the field "Grading action" to "Reveal student identities" And I set the field "Grading action" to "Reveal student identities"
And I press "Continue" And I press "Continue"
@ -78,17 +87,23 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
@javascript @javascript
Scenario: Student identities are revealed before releasing the grades. Scenario: Student identities are revealed before releasing the grades.
When I click on "Grade Participant " "link" in the "I'm the student's first submission" "table_row" When I click on "Grade" "link" in the "I'm the student's first submission" "table_row"
And I set the field "Marking workflow state" to "Ready for release" And I set the field "Marking workflow state" to "Ready for release"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Ready for release" in the "I'm the student's first submission" "table_row" And I should see "Ready for release" in the "I'm the student's first submission" "table_row"
And I set the field "Grading action" to "Reveal student identities" And I set the field "Grading action" to "Reveal student identities"
And I press "Continue" And I press "Continue"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the field "Marking workflow state" to "Released" And I set the field "Marking workflow state" to "Released"
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
And I should see "Released" in the "Student 1" "table_row" And I should see "Released" in the "Student 1" "table_row"
And I log out And I log out
And I log in as "student1" And I log in as "student1"

View file

@ -1,4 +1,4 @@
@mod @mod_assign @mod @mod_assign @javascript
Feature: In an assignment, students can comment in their submissions Feature: In an assignment, students can comment in their submissions
In order to refine assignment submissions In order to refine assignment submissions
As a student As a student
@ -17,7 +17,6 @@ Feature: In an assignment, students can comment in their submissions
| teacher1 | C1 | editingteacher | | teacher1 | C1 | editingteacher |
| student1 | C1 | student | | student1 | C1 | student |
@javascript
Scenario: Student comments an assignment submission Scenario: Student comments an assignment submission
Given the following "activities" exist: Given the following "activities" exist:
| activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled |
@ -46,7 +45,6 @@ Feature: In an assignment, students can comment in their submissions
And I should see "Second student comment" And I should see "Second student comment"
And I should not see "First student comment" And I should not see "First student comment"
@javascript
Scenario: Teacher can comment on an offline assignment Scenario: Teacher can comment on an offline assignment
Given the following "activities" exist: Given the following "activities" exist:
| activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | assignmentsubmission_file_enabled | assignfeedback_comments_enabled | | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | assignmentsubmission_file_enabled | assignfeedback_comments_enabled |
@ -54,13 +52,16 @@ Feature: In an assignment, students can comment in their submissions
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
When I set the following fields to these values: When I set the following fields to these values:
| Grade out of 100 | 50 | | Grade out of 100 | 50 |
| Feedback comments | I'm the teacher feedback | | Feedback comments | I'm the teacher feedback |
And I press "Save changes" And I press "Save changes"
And I press "Continue" And I press "Ok"
And I click on "Edit settings" "link"
And I follow "Test assignment name"
And I follow "View all submissions"
Then I should see "50.00" in the "Student 1" "table_row" Then I should see "50.00" in the "Student 1" "table_row"
And I should see "I'm the teacher feedback" in the "Student 1" "table_row" And I should see "I'm the teacher feedback" in the "Student 1" "table_row"
@ -71,15 +72,14 @@ Feature: In an assignment, students can comment in their submissions
And I log in as "teacher1" And I log in as "teacher1"
And I follow "Course 1" And I follow "Course 1"
And I follow "Test assignment name" And I follow "Test assignment name"
And I follow "View/grade all submissions" And I follow "View all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row" And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Grade out of 100 | 0 | | Grade out of 100 | 0 |
And I press "Save changes" And I press "Save changes"
And I should see "The grade changes were saved" And I should see "The changes to the grade and feedback were saved"
And I press "Continue" And I press "Ok"
When I click on "Grade Student 1" "link" in the "Student 1" "table_row"
And I set the following fields to these values: And I set the following fields to these values:
| Feedback comments | I'm the teacher feedback | | Feedback comments | I'm the teacher feedback |
And I press "Save changes" And I press "Save changes"
Then I should see "The grade changes were saved" Then I should see "The changes to the grade and feedback were saved"

View file

@ -90,7 +90,7 @@ Feature: Submit assignment without group
And I follow "Allow default group" And I follow "Allow default group"
And I should see "1" in the "Groups" "table_row" And I should see "1" in the "Groups" "table_row"
And I should not see "The setting 'Require group to make submission\' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions." And I should not see "The setting 'Require group to make submission\' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions."
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Default group" in the "Student 1" "table_row" And I should see "Default group" in the "Student 1" "table_row"
And I should see "Default group" in the "Student 2" "table_row" And I should see "Default group" in the "Student 2" "table_row"
And I should see "Submitted for grading" in the "Student 1" "table_row" And I should see "Submitted for grading" in the "Student 1" "table_row"
@ -100,7 +100,7 @@ Feature: Submit assignment without group
And I follow "Require group membership" And I follow "Require group membership"
And I should see "0" in the "Groups" "table_row" And I should see "0" in the "Groups" "table_row"
And I should see "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions." And I should see "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions."
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Not a member of any group, so unable to make submissions." in the "Student 1" "table_row" And I should see "Not a member of any group, so unable to make submissions." in the "Student 1" "table_row"
And I should see "Not a member of any group, so unable to make submissions." in the "Student 2" "table_row" And I should see "Not a member of any group, so unable to make submissions." in the "Student 2" "table_row"
And I should not see "Submitted for grading" in the "Student 1" "table_row" And I should not see "Submitted for grading" in the "Student 1" "table_row"
@ -110,7 +110,7 @@ Feature: Submit assignment without group
And I follow "Require group membership" And I follow "Require group membership"
And I should see "1" in the "Groups" "table_row" And I should see "1" in the "Groups" "table_row"
And I should not see "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions." And I should not see "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions."
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Group 1" in the "Student 1" "table_row" And I should see "Group 1" in the "Student 1" "table_row"
And I should see "Group 1" in the "Student 2" "table_row" And I should see "Group 1" in the "Student 2" "table_row"
And I should see "Submitted for grading" in the "Student 1" "table_row" And I should see "Submitted for grading" in the "Student 1" "table_row"
@ -128,5 +128,5 @@ Feature: Submit assignment without group
And I follow "Course 3" And I follow "Course 3"
And I follow "Require group membership" And I follow "Require group membership"
And I should see "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions." And I should see "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions."
And I follow "View/grade all submissions" And I follow "View all submissions"
And I should see "Member of more than one group, so unable to make submissions." in the "Student 3" "table_row" And I should see "Member of more than one group, so unable to make submissions." in the "Student 3" "table_row"

View file

@ -481,6 +481,8 @@ fieldset.coursesearchbox label {
} }
} }
/** /**
* Make forms vertical when the screen is less than 1474px AND both side-pre and side-post contain blocks. * Make forms vertical when the screen is less than 1474px AND both side-pre and side-post contain blocks.
* This is an extra special media rule. * This is an extra special media rule.