Merge branch 'MDL-52954-master' of git://github.com/damyon/moodle
This commit is contained in:
@@ -12,6 +12,7 @@ $temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('patht
|
||||
$temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'), new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
|
||||
$temp->add(new admin_setting_configexecutable('pathtounoconv', new lang_string('pathtounoconv', 'admin'), new lang_string('pathtounoconv_help', 'admin'), '/usr/bin/unoconv'));
|
||||
$ADMIN->add('server', $temp);
|
||||
|
||||
|
||||
|
||||
@@ -840,6 +840,12 @@ $CFG->admin = 'admin';
|
||||
// Note that, for now, this only used by the profiling features
|
||||
// (Development->Profiling) built into Moodle.
|
||||
// $CFG->pathtodot = '';
|
||||
//
|
||||
// Path to unoconv.
|
||||
// Probably something like /usr/bin/unoconv. Used as a fallback to convert between document formats.
|
||||
// Unoconv is used convert between file formats supported by LibreOffice.
|
||||
// Use a recent version of unoconv ( >= 0.7 ), older versions have trouble running from a webserver.
|
||||
// $CFG->pathtounoconv = '';
|
||||
|
||||
//=========================================================================
|
||||
// ALL DONE! To continue installation, visit your main page with a browser
|
||||
|
||||
@@ -647,21 +647,25 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$checked_s2 = $checked;
|
||||
}
|
||||
|
||||
$radio = html_writer::tag('input', get_string('showmarkerdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
$radio1 = html_writer::tag('input', get_string('showmarkerdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
'name' => 'showmarkerdesc',
|
||||
'value' => "true")+$checked1);
|
||||
$radio .= html_writer::tag('input', get_string('hidemarkerdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
$radio1 = html_writer::tag('label', $radio1);
|
||||
$radio2 = html_writer::tag('input', get_string('hidemarkerdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
'name' => 'showmarkerdesc',
|
||||
'value' => "false")+$checked2);
|
||||
$output .= html_writer::tag('div', $radio, array('class' => 'showmarkerdesc'));
|
||||
$radio2 = html_writer::tag('label', $radio2);
|
||||
$output .= html_writer::tag('div', $radio1 . $radio2, array('class' => 'showmarkerdesc'));
|
||||
|
||||
$radio = html_writer::tag('input', get_string('showstudentdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
$radio1 = html_writer::tag('input', get_string('showstudentdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
'name' => 'showstudentdesc',
|
||||
'value' => "true")+$checked_s1);
|
||||
$radio .= html_writer::tag('input', get_string('hidestudentdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
$radio1 = html_writer::tag('label', $radio1);
|
||||
$radio2 = html_writer::tag('input', get_string('hidestudentdesc', 'gradingform_guide'), array('type' => 'radio',
|
||||
'name' => 'showstudentdesc',
|
||||
'value' => "false")+$checked_s2);
|
||||
$output .= html_writer::tag('div', $radio, array('class' => 'showstudentdesc'));
|
||||
$radio2 = html_writer::tag('label', $radio2);
|
||||
$output .= html_writer::tag('div', $radio1 . $radio2, array('class' => 'showstudentdesc'));
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
@@ -775,4 +779,4 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,6 +790,8 @@ $string['pathtopgdumpinvalid'] = 'Invalid path to pg_dump - either wrong path or
|
||||
$string['pathtopsql'] = 'Path to psql';
|
||||
$string['pathtopsqldesc'] = 'This is only necessary to enter if you have more than one psql on your system (for example if you have more than one version of postgresql installed)';
|
||||
$string['pathtopsqlinvalid'] = 'Invalid path to psql - either wrong path or not executable';
|
||||
$string['pathtounoconv'] = 'Path to unoconv document converter';
|
||||
$string['pathtounoconv_help'] = 'Path to unoconv document converter. This is an executable that is capable of converting between document formats supported by LibreOffice. This is optional, but if specified, Moodle will use it to automatically convert between document formats. This is used to support a wider range of input files for the assignment annotate PDF feature.';
|
||||
$string['pcreunicodewarning'] = 'It is strongly recommended to use PCRE PHP extension that is compatible with Unicode characters.';
|
||||
$string['perfdebug'] = 'Performance info';
|
||||
$string['performance'] = 'Performance';
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -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});
|
||||
@@ -778,8 +778,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
throttleTimeout = window.setTimeout(handler.bind(this, e), 300);
|
||||
};
|
||||
// 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));
|
||||
arrowElement.on("click", handler);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
+1
-1
@@ -166,7 +166,7 @@ function behat_clean_init_config() {
|
||||
'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
|
||||
'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
|
||||
'proxybypass', 'theme', 'pathtogs', 'pathtodu', 'aspellpath', 'pathtodot', 'skiplangupgrade',
|
||||
'altcacheconfigpath'
|
||||
'altcacheconfigpath', 'pathtounoconv'
|
||||
));
|
||||
|
||||
// Add extra allowed settings.
|
||||
|
||||
@@ -428,6 +428,7 @@ $functions = array(
|
||||
'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()',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update',
|
||||
),
|
||||
|
||||
|
||||
+15
-1
@@ -820,6 +820,14 @@ EditorAutosave.prototype = {
|
||||
*/
|
||||
autosaveInstance: null,
|
||||
|
||||
/**
|
||||
* Autosave Timer.
|
||||
*
|
||||
* @property autosaveTimer
|
||||
* @type object
|
||||
*/
|
||||
autosaveTimer: null,
|
||||
|
||||
/**
|
||||
* Initialize the autosave process
|
||||
*
|
||||
@@ -904,7 +912,7 @@ EditorAutosave.prototype = {
|
||||
// Now setup the timer for periodic saves.
|
||||
|
||||
var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
|
||||
Y.later(delay, this, this.saveDraft, false, true);
|
||||
this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
|
||||
|
||||
// Now setup the listener for form submission.
|
||||
form = this.textarea.ancestor('form');
|
||||
@@ -968,6 +976,12 @@ EditorAutosave.prototype = {
|
||||
*/
|
||||
saveDraft: function() {
|
||||
var url, params;
|
||||
|
||||
if (!this.editor.getDOMNode()) {
|
||||
// Stop autosaving if the editor was removed from the page.
|
||||
this.autosaveTimer.cancel();
|
||||
return;
|
||||
}
|
||||
// Only copy the text from the div to the textarea if the textarea is not currently visible.
|
||||
if (!this.editor.get('hidden')) {
|
||||
this.updateOriginal();
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
+15
-1
@@ -815,6 +815,14 @@ EditorAutosave.prototype = {
|
||||
*/
|
||||
autosaveInstance: null,
|
||||
|
||||
/**
|
||||
* Autosave Timer.
|
||||
*
|
||||
* @property autosaveTimer
|
||||
* @type object
|
||||
*/
|
||||
autosaveTimer: null,
|
||||
|
||||
/**
|
||||
* Initialize the autosave process
|
||||
*
|
||||
@@ -897,7 +905,7 @@ EditorAutosave.prototype = {
|
||||
// Now setup the timer for periodic saves.
|
||||
|
||||
var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
|
||||
Y.later(delay, this, this.saveDraft, false, true);
|
||||
this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
|
||||
|
||||
// Now setup the listener for form submission.
|
||||
form = this.textarea.ancestor('form');
|
||||
@@ -961,6 +969,12 @@ EditorAutosave.prototype = {
|
||||
*/
|
||||
saveDraft: function() {
|
||||
var url, params;
|
||||
|
||||
if (!this.editor.getDOMNode()) {
|
||||
// Stop autosaving if the editor was removed from the page.
|
||||
this.autosaveTimer.cancel();
|
||||
return;
|
||||
}
|
||||
// Only copy the text from the div to the textarea if the textarea is not currently visible.
|
||||
if (!this.editor.get('hidden')) {
|
||||
this.updateOriginal();
|
||||
|
||||
+15
-1
@@ -99,6 +99,14 @@ EditorAutosave.prototype = {
|
||||
*/
|
||||
autosaveInstance: null,
|
||||
|
||||
/**
|
||||
* Autosave Timer.
|
||||
*
|
||||
* @property autosaveTimer
|
||||
* @type object
|
||||
*/
|
||||
autosaveTimer: null,
|
||||
|
||||
/**
|
||||
* Initialize the autosave process
|
||||
*
|
||||
@@ -183,7 +191,7 @@ EditorAutosave.prototype = {
|
||||
// Now setup the timer for periodic saves.
|
||||
|
||||
var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
|
||||
Y.later(delay, this, this.saveDraft, false, true);
|
||||
this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
|
||||
|
||||
// Now setup the listener for form submission.
|
||||
form = this.textarea.ancestor('form');
|
||||
@@ -247,6 +255,12 @@ EditorAutosave.prototype = {
|
||||
*/
|
||||
saveDraft: function() {
|
||||
var url, params;
|
||||
|
||||
if (!this.editor.getDOMNode()) {
|
||||
// Stop autosaving if the editor was removed from the page.
|
||||
this.autosaveTimer.cancel();
|
||||
return;
|
||||
}
|
||||
// Only copy the text from the div to the textarea if the textarea is not currently visible.
|
||||
if (!this.editor.get('hidden')) {
|
||||
this.updateOriginal();
|
||||
|
||||
@@ -53,6 +53,9 @@ class file_storage {
|
||||
private $dirpermissions;
|
||||
/** @var int Permissions for new files */
|
||||
private $filepermissions;
|
||||
/** @var array List of formats supported by unoconv */
|
||||
private $unoconvformats;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor - do not use directly use {@link get_file_storage()} call instead.
|
||||
@@ -156,6 +159,129 @@ class file_storage {
|
||||
return $storedfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get converted document.
|
||||
*
|
||||
* Get an alternate version of the specified document, if it is possible to convert.
|
||||
*
|
||||
* @param stored_file $file the file we want to preview
|
||||
* @param string $format The desired format - e.g. 'pdf'. Formats are specified by file extension.
|
||||
* @return stored_file|bool false if unable to create the conversion, stored file otherwise
|
||||
*/
|
||||
public function get_converted_document(stored_file $file, $format) {
|
||||
|
||||
$context = context_system::instance();
|
||||
$path = '/' . $format . '/';
|
||||
$conversion = $this->get_file($context->id, 'core', 'documentconversion', 0, $path, $file->get_contenthash());
|
||||
|
||||
if (!$conversion) {
|
||||
$conversion = $this->create_converted_document($file, $format);
|
||||
if (!$conversion) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the format is supported.
|
||||
*
|
||||
* @param string $format The desired format - e.g. 'pdf'. Formats are specified by file extension.
|
||||
* @return bool - True if the format is supported for input.
|
||||
*/
|
||||
protected function is_format_supported_by_unoconv($format) {
|
||||
global $CFG;
|
||||
|
||||
if (!isset($this->unoconvformats)) {
|
||||
// Ask unoconv for it's list of supported document formats.
|
||||
$cmd = escapeshellcmd(trim($CFG->pathtounoconv)) . ' --show';
|
||||
$pipes = array();
|
||||
$pipesspec = array(2 => array('pipe', 'w'));
|
||||
$proc = proc_open($cmd, $pipesspec, $pipes);
|
||||
$programoutput = stream_get_contents($pipes[2]);
|
||||
fclose($pipes[2]);
|
||||
proc_close($proc);
|
||||
$matches = array();
|
||||
preg_match_all('/\[\.(.*)\]/', $programoutput, $matches);
|
||||
|
||||
$this->unoconvformats = $matches[1];
|
||||
$this->unoconvformats = array_unique($this->unoconvformats);
|
||||
}
|
||||
|
||||
$sanitized = trim(core_text::strtolower($format));
|
||||
return in_array($sanitized, $this->unoconvformats);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform a file format conversion on the specified document.
|
||||
*
|
||||
* @param stored_file $file the file we want to preview
|
||||
* @param string $format The desired format - e.g. 'pdf'. Formats are specified by file extension.
|
||||
* @return stored_file|bool false if unable to create the conversion, stored file otherwise
|
||||
*/
|
||||
protected function create_converted_document(stored_file $file, $format) {
|
||||
global $CFG;
|
||||
|
||||
if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
|
||||
// No conversions are possible, sorry.
|
||||
return false;
|
||||
}
|
||||
|
||||
$fileextension = core_text::strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
|
||||
if (!self::is_format_supported_by_unoconv($fileextension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::is_format_supported_by_unoconv($format)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the file to the local tmp dir.
|
||||
$tmp = make_request_directory();
|
||||
$localfilename = $file->get_filename();
|
||||
// Safety.
|
||||
$localfilename = clean_param($localfilename, PARAM_FILE);
|
||||
|
||||
$filename = $tmp . '/' . $localfilename;
|
||||
$file->copy_content_to($filename);
|
||||
|
||||
$newtmpfile = pathinfo($filename, PATHINFO_FILENAME) . '.' . $format;
|
||||
|
||||
// Safety.
|
||||
$newtmpfile = $tmp . '/' . clean_param($newtmpfile, PARAM_FILE);
|
||||
|
||||
$cmd = escapeshellcmd(trim($CFG->pathtounoconv)) . ' ' .
|
||||
escapeshellarg('-f') . ' ' .
|
||||
escapeshellarg($format) . ' ' .
|
||||
escapeshellarg('-o') . ' ' .
|
||||
escapeshellarg($newtmpfile) . ' ' .
|
||||
escapeshellarg($filename);
|
||||
|
||||
$e = file_exists($filename);
|
||||
$output = null;
|
||||
$currentdir = getcwd();
|
||||
chdir($tmp);
|
||||
$result = exec($cmd, $output);
|
||||
chdir($currentdir);
|
||||
if (!file_exists($newtmpfile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$context = context_system::instance();
|
||||
$record = array(
|
||||
'contextid' => $context->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/' . $format . '/',
|
||||
'filename' => $file->get_contenthash(),
|
||||
);
|
||||
|
||||
return $this->create_file_from_pathname($record, $newtmpfile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image file that represent the given stored file as a preview
|
||||
*
|
||||
@@ -2282,6 +2408,26 @@ class file_storage {
|
||||
$rs->close();
|
||||
mtrace('done.');
|
||||
|
||||
// Remove orphaned converted files (that is files in the core documentconversion filearea without
|
||||
// the existing original file).
|
||||
mtrace('Deleting orphaned document conversion files... ', '');
|
||||
cron_trace_time_and_memory();
|
||||
$sql = "SELECT p.*
|
||||
FROM {files} p
|
||||
LEFT JOIN {files} o ON (p.filename = o.contenthash)
|
||||
WHERE p.contextid = ? AND p.component = 'core' AND p.filearea = 'documentconversion' AND p.itemid = 0
|
||||
AND o.id IS NULL";
|
||||
$syscontext = context_system::instance();
|
||||
$rs = $DB->get_recordset_sql($sql, array($syscontext->id));
|
||||
foreach ($rs as $orphan) {
|
||||
$file = $this->get_file_instance($orphan);
|
||||
if (!$file->is_directory()) {
|
||||
$file->delete();
|
||||
}
|
||||
}
|
||||
$rs->close();
|
||||
mtrace('done.');
|
||||
|
||||
// remove trash pool files once a day
|
||||
// if you want to disable purging of trash put $CFG->fileslastcleanup=time(); into config.php
|
||||
if (empty($CFG->fileslastcleanup) or $CFG->fileslastcleanup < time() - 60*60*24) {
|
||||
|
||||
+11
-2
@@ -131,6 +131,9 @@ abstract class moodleform {
|
||||
/** @var array globals workaround */
|
||||
protected $_customdata;
|
||||
|
||||
/** @var array submitted form data when using mforms with ajax */
|
||||
protected $_ajaxformdata;
|
||||
|
||||
/** @var object definition_after_data executed flag */
|
||||
protected $_definition_finalized = false;
|
||||
|
||||
@@ -156,8 +159,10 @@ abstract class moodleform {
|
||||
* it if you don't need to as the target attribute is deprecated in xhtml strict.
|
||||
* @param mixed $attributes you can pass a string of html attributes here or an array.
|
||||
* @param bool $editable
|
||||
* @param array $ajaxformdata Forms submitted via ajax, must pass their data here, instead of relying on _GET and _POST.
|
||||
*/
|
||||
public function __construct($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) {
|
||||
public function __construct($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true,
|
||||
$ajaxformdata=null) {
|
||||
global $CFG, $FULLME;
|
||||
// no standard mform in moodle should allow autocomplete with the exception of user signup
|
||||
if (empty($attributes)) {
|
||||
@@ -170,6 +175,7 @@ abstract class moodleform {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (empty($action)){
|
||||
// do not rely on PAGE->url here because dev often do not setup $actualurl properly in admin_externalpage_setup()
|
||||
$action = strip_querystring($FULLME);
|
||||
@@ -183,6 +189,7 @@ abstract class moodleform {
|
||||
// Assign custom data first, so that get_form_identifier can use it.
|
||||
$this->_customdata = $customdata;
|
||||
$this->_formname = $this->get_form_identifier();
|
||||
$this->_ajaxformdata = $ajaxformdata;
|
||||
|
||||
$this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes);
|
||||
if (!$editable){
|
||||
@@ -272,7 +279,9 @@ abstract class moodleform {
|
||||
*/
|
||||
function _process_submission($method) {
|
||||
$submission = array();
|
||||
if ($method == 'post') {
|
||||
if (!empty($this->_ajaxformdata)) {
|
||||
$submission = $this->_ajaxformdata;
|
||||
} else if ($method == 'post') {
|
||||
if (!empty($_POST)) {
|
||||
$submission = $_POST;
|
||||
}
|
||||
|
||||
@@ -185,7 +185,8 @@ $CFG->dboptions = isset($CFG->phpunit_dboptions) ? $CFG->phpunit_dboptions : $CF
|
||||
$allowed = array('wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
|
||||
'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions',
|
||||
'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass', // keep proxy settings from config.php
|
||||
'altcacheconfigpath', 'pathtogs', 'pathtodu', 'aspellpath', 'pathtodot'
|
||||
'altcacheconfigpath', 'pathtogs', 'pathtodu', 'aspellpath', 'pathtodot',
|
||||
'pathtounoconv'
|
||||
);
|
||||
$productioncfg = (array)$CFG;
|
||||
$CFG = new stdClass();
|
||||
|
||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h1>My First Heading</h1>
|
||||
|
||||
<p>My first paragraph.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test unoconv functionality.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2016 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* A set of tests for some of the unoconv functionality within Moodle.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2016 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_unoconv_testcase extends advanced_testcase {
|
||||
|
||||
/** @var $testfile1 */
|
||||
private $testfile1 = null;
|
||||
/** @var $testfile2 */
|
||||
private $testfile2 = null;
|
||||
|
||||
public function setUp() {
|
||||
$this->fixturepath = __DIR__ . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$fs = get_file_storage();
|
||||
$filerecord = array(
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'test',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'test.html'
|
||||
);
|
||||
$teststring = file_get_contents($this->fixturepath . DIRECTORY_SEPARATOR . 'unoconv-source.html');
|
||||
$this->testfile1 = $fs->create_file_from_string($filerecord, $teststring);
|
||||
|
||||
$filerecord = array(
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'test',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'test.docx'
|
||||
);
|
||||
$teststring = file_get_contents($this->fixturepath . DIRECTORY_SEPARATOR . 'unoconv-source.docx');
|
||||
$this->testfile2 = $fs->create_file_from_string($filerecord, $teststring);
|
||||
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
public function test_generate_pdf() {
|
||||
global $CFG;
|
||||
|
||||
if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
|
||||
// No conversions are possible, sorry.
|
||||
return $this->markTestSkipped();
|
||||
}
|
||||
$fs = get_file_storage();
|
||||
|
||||
$result = $fs->get_converted_document($this->testfile1, 'pdf');
|
||||
$this->assertNotFalse($result);
|
||||
$this->assertSame($result->get_mimetype(), 'application/pdf');
|
||||
$this->assertGreaterThan(0, $result->get_filesize());
|
||||
$result = $fs->get_converted_document($this->testfile2, 'pdf');
|
||||
$this->assertNotFalse($result);
|
||||
$this->assertSame($result->get_mimetype(), 'application/pdf');
|
||||
$this->assertGreaterThan(0, $result->get_filesize());
|
||||
}
|
||||
|
||||
public function test_generate_markdown() {
|
||||
global $CFG;
|
||||
|
||||
if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
|
||||
// No conversions are possible, sorry.
|
||||
return $this->markTestSkipped();
|
||||
}
|
||||
$fs = get_file_storage();
|
||||
|
||||
$result = $fs->get_converted_document($this->testfile1, 'txt');
|
||||
$this->assertNotFalse($result);
|
||||
$this->assertSame($result->get_mimetype(), 'text/plain');
|
||||
$this->assertGreaterThan(0, $result->get_filesize());
|
||||
$result = $fs->get_converted_document($this->testfile2, 'txt');
|
||||
$this->assertNotFalse($result);
|
||||
$this->assertSame($result->get_mimetype(), 'text/plain');
|
||||
$this->assertGreaterThan(0, $result->get_filesize());
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -56,7 +56,10 @@ information provided here is intended especially for developers.
|
||||
- upgrade_course_modules_sequences()
|
||||
- upgrade_grade_item_fix_sortorder()
|
||||
- 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:
|
||||
<frankenstyle>_extend_navigation_user(navigation_node $parentnode, stdClass $user,
|
||||
context_user $context, stdClass $course,
|
||||
|
||||
+1
@@ -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});
|
||||
@@ -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
@@ -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});
|
||||
+1
@@ -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});
|
||||
@@ -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});
|
||||
@@ -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)}}});
|
||||
@@ -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 <[email protected]>
|
||||
* @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;
|
||||
});
|
||||
@@ -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 <[email protected]>
|
||||
* @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);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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 <[email protected]>
|
||||
* @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;
|
||||
});
|
||||
@@ -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 <[email protected]>
|
||||
* @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;
|
||||
});
|
||||
@@ -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 <[email protected]>
|
||||
* @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;
|
||||
});
|
||||
@@ -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 <[email protected]>
|
||||
* @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;
|
||||
});
|
||||
@@ -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 <[email protected]>
|
||||
* @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);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* @var $userid - The initial user id.
|
||||
*/
|
||||
public $userid = 0;
|
||||
|
||||
/**
|
||||
* @var $groupid - The initial group id.
|
||||
*/
|
||||
public $groupid = 0;
|
||||
|
||||
/**
|
||||
* @var $assignment - 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -190,4 +190,24 @@ $functions = array(
|
||||
'capabilities' => 'mod/assign:view',
|
||||
'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'
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1506,6 +1506,91 @@ class mod_assign_external extends external_api {
|
||||
return new external_warnings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the parameters for submit_grading_form webservice.
|
||||
* @return external_external_function_parameters
|
||||
* @since Moodle 3.1
|
||||
*/
|
||||
public static function submit_grading_form_parameters() {
|
||||
return new external_function_parameters(
|
||||
array(
|
||||
'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
|
||||
'userid' => new external_value(PARAM_INT, 'The user id the submission belongs to'),
|
||||
'jsonformdata' => new external_value(PARAM_RAW, 'The data from the grading form, encoded as a json array')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the logged in users assignment for grading.
|
||||
*
|
||||
* @param int $assignmentid The id of the assignment
|
||||
* @param int $userid The id of the user the submission belongs to.
|
||||
* @param string $jsonformdata The data from the form, encoded as a json array.
|
||||
* @return array of warnings to indicate any errors.
|
||||
* @since Moodle 2.6
|
||||
*/
|
||||
public static function submit_grading_form($assignmentid, $userid, $jsonformdata) {
|
||||
global $CFG, $USER;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/assign/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/gradeform.php');
|
||||
|
||||
$params = self::validate_parameters(self::submit_grading_form_parameters(),
|
||||
array(
|
||||
'assignmentid' => $assignmentid,
|
||||
'userid' => $userid,
|
||||
'jsonformdata' => $jsonformdata
|
||||
));
|
||||
|
||||
$cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
|
||||
$context = context_module::instance($cm->id);
|
||||
self::validate_context($context);
|
||||
|
||||
$assignment = new assign($context, $cm, null);
|
||||
|
||||
$serialiseddata = json_decode($params['jsonformdata']);
|
||||
|
||||
$data = array();
|
||||
parse_str($serialiseddata, $data);
|
||||
|
||||
$warnings = array();
|
||||
|
||||
$options = array(
|
||||
'userid' => $params['userid'],
|
||||
'attemptnumber' => $data['attemptnumber'],
|
||||
'rownum' => 0,
|
||||
'gradingpanel' => true
|
||||
);
|
||||
|
||||
$customdata = (object) $data;
|
||||
$formparams = array($assignment, $customdata, $options);
|
||||
|
||||
// Data is injected into the form by the last param for the constructor.
|
||||
$mform = new mod_assign_grade_form(null, $formparams, 'post', '', null, true, $data);
|
||||
$validateddata = $mform->get_data();
|
||||
|
||||
if ($validateddata) {
|
||||
$assignment->save_grade($params['userid'], $validateddata);
|
||||
} else {
|
||||
$warnings[] = self::generate_warning($params['assignmentid'],
|
||||
'couldnotsavegrade',
|
||||
'Form validation failed.');
|
||||
}
|
||||
|
||||
|
||||
return $warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the return for submit_grading_form
|
||||
* @return external_external_function_parameters
|
||||
* @since Moodle 3.1
|
||||
*/
|
||||
public static function submit_grading_form_returns() {
|
||||
return new external_warnings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the parameters for submit_for_grading
|
||||
* @return external_external_function_parameters
|
||||
@@ -2185,6 +2270,7 @@ class mod_assign_external extends external_api {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the parameters for view_submission_status.
|
||||
*
|
||||
@@ -2486,4 +2572,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', 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),
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace assignfeedback_editpdf;
|
||||
|
||||
use DOMDocument;
|
||||
|
||||
/**
|
||||
* Functions for generating the annotated pdf.
|
||||
*
|
||||
@@ -40,6 +42,8 @@ class document_services {
|
||||
const FINAL_PDF_FILEAREA = 'download';
|
||||
/** File area for combined pdf */
|
||||
const COMBINED_PDF_FILEAREA = 'combined';
|
||||
/** File area for importing html */
|
||||
const IMPORT_HTML_FILEAREA = 'importhtml';
|
||||
/** File area for page images */
|
||||
const PAGE_IMAGE_FILEAREA = 'pages';
|
||||
/** File area for readonly page images */
|
||||
@@ -49,6 +53,30 @@ class document_services {
|
||||
/** Filename for combined pdf */
|
||||
const COMBINED_PDF_FILENAME = 'combined.pdf';
|
||||
|
||||
/** Base64 encoded blank pdf. This is the most reliable/fastest way to generate a blank pdf. */
|
||||
const BLANK_PDF_BASE64 = <<<EOD
|
||||
JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURl
|
||||
Y29kZT4+CnN0cmVhbQp4nDPQM1Qo5ypUMFAwALJMLU31jBQsTAz1LBSKUrnCtRTyuAIVAIcdB3IK
|
||||
ZW5kc3RyZWFtCmVuZG9iagoKMyAwIG9iago0MgplbmRvYmoKCjUgMCBvYmoKPDwKPj4KZW5kb2Jq
|
||||
Cgo2IDAgb2JqCjw8L0ZvbnQgNSAwIFIKL1Byb2NTZXRbL1BERi9UZXh0XQo+PgplbmRvYmoKCjEg
|
||||
MCBvYmoKPDwvVHlwZS9QYWdlL1BhcmVudCA0IDAgUi9SZXNvdXJjZXMgNiAwIFIvTWVkaWFCb3hb
|
||||
MCAwIDU5NSA4NDJdL0dyb3VwPDwvUy9UcmFuc3BhcmVuY3kvQ1MvRGV2aWNlUkdCL0kgdHJ1ZT4+
|
||||
L0NvbnRlbnRzIDIgMCBSPj4KZW5kb2JqCgo0IDAgb2JqCjw8L1R5cGUvUGFnZXMKL1Jlc291cmNl
|
||||
cyA2IDAgUgovTWVkaWFCb3hbIDAgMCA1OTUgODQyIF0KL0tpZHNbIDEgMCBSIF0KL0NvdW50IDE+
|
||||
PgplbmRvYmoKCjcgMCBvYmoKPDwvVHlwZS9DYXRhbG9nL1BhZ2VzIDQgMCBSCi9PcGVuQWN0aW9u
|
||||
WzEgMCBSIC9YWVogbnVsbCBudWxsIDBdCi9MYW5nKGVuLUFVKQo+PgplbmRvYmoKCjggMCBvYmoK
|
||||
PDwvQ3JlYXRvcjxGRUZGMDA1NzAwNzIwMDY5MDA3NDAwNjUwMDcyPgovUHJvZHVjZXI8RkVGRjAw
|
||||
NEMwMDY5MDA2MjAwNzIwMDY1MDA0RjAwNjYwMDY2MDA2OTAwNjMwMDY1MDAyMDAwMzQwMDJFMDAz
|
||||
ND4KL0NyZWF0aW9uRGF0ZShEOjIwMTYwMjI2MTMyMzE0KzA4JzAwJyk+PgplbmRvYmoKCnhyZWYK
|
||||
MCA5CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDIyNiAwMDAwMCBuIAowMDAwMDAwMDE5IDAw
|
||||
MDAwIG4gCjAwMDAwMDAxMzIgMDAwMDAgbiAKMDAwMDAwMDM2OCAwMDAwMCBuIAowMDAwMDAwMTUx
|
||||
IDAwMDAwIG4gCjAwMDAwMDAxNzMgMDAwMDAgbiAKMDAwMDAwMDQ2NiAwMDAwMCBuIAowMDAwMDAw
|
||||
NTYyIDAwMDAwIG4gCnRyYWlsZXIKPDwvU2l6ZSA5L1Jvb3QgNyAwIFIKL0luZm8gOCAwIFIKL0lE
|
||||
IFsgPEJDN0REQUQwRDQyOTQ1OTQ2OUU4NzJCMjI1ODUyNkU4Pgo8QkM3RERBRDBENDI5NDU5NDY5
|
||||
RTg3MkIyMjU4NTI2RTg+IF0KL0RvY0NoZWNrc3VtIC9BNTYwMEZCMDAzRURCRTg0MTNBNTk3RTZF
|
||||
MURDQzJBRgo+PgpzdGFydHhyZWYKNzM2CiUlRU9GCg==
|
||||
EOD;
|
||||
|
||||
/**
|
||||
* This function will take an int or an assignment instance and
|
||||
* return an assignment instance. It is just for convenience.
|
||||
@@ -84,6 +112,32 @@ class document_services {
|
||||
return sha1($assignmentid . '_' . $userid . '_' . $attemptnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a DOM parser to accurately replace images with their alt text.
|
||||
* @param string $html
|
||||
* @return string New html with no image tags.
|
||||
*/
|
||||
protected static function strip_images($html) {
|
||||
$dom = new DOMDocument();
|
||||
$dom->loadHTML($html);
|
||||
$images = $dom->getElementsByTagName('img');
|
||||
$i = 0;
|
||||
|
||||
for ($i = ($images->length - 1); $i >= 0; $i--) {
|
||||
$node = $images->item($i);
|
||||
|
||||
if ($node->hasAttribute('alt')) {
|
||||
$replacement = ' [ ' . $node->getAttribute('alt') . ' ] ';
|
||||
} else {
|
||||
$replacement = ' ';
|
||||
}
|
||||
|
||||
$text = $dom->createTextNode($replacement);
|
||||
$node->parentNode->replaceChild($text, $node);
|
||||
}
|
||||
return $dom->saveHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will search for all files that can be converted
|
||||
* and concatinated into a PDF (1.4) - for any submission plugin
|
||||
@@ -116,13 +170,38 @@ class document_services {
|
||||
if (!$submission) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
$fs = get_file_storage();
|
||||
// Ask each plugin for it's list of files.
|
||||
foreach ($assignment->get_submission_plugins() as $plugin) {
|
||||
if ($plugin->is_enabled() && $plugin->is_visible()) {
|
||||
$pluginfiles = $plugin->get_files($submission, $user);
|
||||
foreach ($pluginfiles as $filename => $file) {
|
||||
if (($file instanceof \stored_file) && ($file->get_mimetype() === 'application/pdf')) {
|
||||
$files[$filename] = $file;
|
||||
if ($file instanceof \stored_file) {
|
||||
if ($file->get_mimetype() === 'application/pdf') {
|
||||
$files[$filename] = $file;
|
||||
} else if ($convertedfile = $fs->get_converted_document($file, 'pdf')) {
|
||||
$files[$filename] = $convertedfile;
|
||||
}
|
||||
} else {
|
||||
// Create a tmp stored_file from this html string.
|
||||
$file = reset($file);
|
||||
// Strip image tags, because they will not be resolvable.
|
||||
$file = self::strip_images($file);
|
||||
$record = new \stdClass();
|
||||
$record->contextid = $assignment->get_context()->id;
|
||||
$record->component = 'assignfeedback_editpdf';
|
||||
$record->filearea = self::IMPORT_HTML_FILEAREA;
|
||||
$record->itemid = $submission->id;
|
||||
$record->filepath = '/';
|
||||
$record->filename = $plugin->get_type() . '-' . $filename;
|
||||
|
||||
$htmlfile = $fs->create_file_from_string($record, $file);
|
||||
$convertedfile = $fs->get_converted_document($htmlfile, 'pdf');
|
||||
$htmlfile->delete();
|
||||
if ($convertedfile) {
|
||||
$files[$filename] = $convertedfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,11 +326,7 @@ class document_services {
|
||||
}
|
||||
|
||||
if (!$files) {
|
||||
// This was a blank pdf.
|
||||
$blankpdf = new pdf();
|
||||
$content = $blankpdf->Output(self::COMBINED_PDF_FILENAME, 'S');
|
||||
$file = $fs->create_file_from_string($record, $content);
|
||||
$blankpdf->Close(); // No real need to close this pdf, because it has been outputted, but for clarity.
|
||||
$file = $fs->create_file_from_string($record, base64_decode(self::BLANK_PDF_BASE64));
|
||||
} else {
|
||||
// This was a combined pdf.
|
||||
$file = $fs->create_file_from_pathname($record, $tmpfile);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* An event observer.
|
||||
* @copyright 2016 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,10 +118,7 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
|
||||
array('id'=>$linkid, 'class'=>'btn', 'href'=>'#'));
|
||||
}
|
||||
$links = $launcheditorlink;
|
||||
|
||||
$links .= html_writer::tag('div',
|
||||
get_string('unsavedchanges', 'assignfeedback_editpdf'),
|
||||
array('class'=>'assignfeedback_editpdf_unsavedchanges warning'));
|
||||
$html .= '<input type="hidden" name="assignfeedback_editpdf_haschanges" value="false"/>';
|
||||
|
||||
$html .= html_writer::div($links, 'visibleifjs');
|
||||
$header = get_string('pluginname', 'assignfeedback_editpdf');
|
||||
@@ -129,6 +126,7 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
|
||||
// Create the page navigation.
|
||||
$navigation1 = '';
|
||||
$navigation2 = '';
|
||||
$navigation3 = '';
|
||||
|
||||
// Pick the correct arrow icons for right to left mode.
|
||||
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 = html_writer::div($navigation2, 'navigation-search', array('role'=>'navigation'));
|
||||
|
||||
|
||||
$toolbar1 = '';
|
||||
$toolbar2 = '';
|
||||
$toolbar3 = '';
|
||||
@@ -210,7 +209,16 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
|
||||
|
||||
$canvas = html_writer::div($loading, 'drawingcanvas');
|
||||
$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 = '';
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?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 convert submissions to pdf in the background.
|
||||
* @copyright 2016 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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',
|
||||
),
|
||||
);
|
||||
Regular → Executable
+12
-2
@@ -1,5 +1,5 @@
|
||||
<?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"
|
||||
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -59,5 +59,15 @@
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</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>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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 editpdf scheduled tasks.
|
||||
*
|
||||
* @package mod_assign
|
||||
* @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' => '*'
|
||||
),
|
||||
);
|
||||
@@ -30,7 +30,9 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @return bool
|
||||
*/
|
||||
function xmldb_assignfeedback_editpdf_upgrade($oldversion) {
|
||||
global $CFG;
|
||||
global $CFG, $DB;
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
// Moodle v2.8.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
@@ -41,5 +43,27 @@ function xmldb_assignfeedback_editpdf_upgrade($oldversion) {
|
||||
// Moodle v3.0.0 release upgrade line.
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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['toolbarbutton'] = '{$a->tool} {$a->shortcut}';
|
||||
$string['tool'] = 'Tool';
|
||||
$string['unsavedchanges'] = 'Unsaved changes';
|
||||
$string['viewfeedbackonline'] = 'View annotated PDF...';
|
||||
$string['white'] = 'White';
|
||||
$string['yellow'] = 'Yellow';
|
||||
$string['draftchangessaved'] = 'Draft annotations saved';
|
||||
$string['preparesubmissionsforannotation'] = 'Prepare submissions for annotation';
|
||||
|
||||
@@ -168,21 +168,16 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
|
||||
$attempt = $grade->attemptnumber;
|
||||
}
|
||||
|
||||
$files = document_services::list_compatible_submission_files_for_attempt($this->assignment, $userid, $attempt);
|
||||
// Only show the editor if there was a compatible file submitted.
|
||||
if (count($files)) {
|
||||
$renderer = $PAGE->get_renderer('assignfeedback_editpdf');
|
||||
|
||||
$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);
|
||||
$mform->addHelpButton('editpdf', 'editpdf', 'assignfeedback_editpdf');
|
||||
$mform->addElement('hidden', 'editpdf_source_userid', $userid);
|
||||
$mform->setType('editpdf_source_userid', PARAM_INT);
|
||||
$mform->setConstant('editpdf_source_userid', $userid);
|
||||
}
|
||||
$html = $renderer->render($widget);
|
||||
$mform->addElement('static', 'editpdf', get_string('editpdf', 'assignfeedback_editpdf'), $html);
|
||||
$mform->addHelpButton('editpdf', 'editpdf', 'assignfeedback_editpdf');
|
||||
$mform->addElement('hidden', '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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,20 @@
|
||||
cursor: crosshair;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #ccc;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.assignfeedback_editpdf_widget .moodle-dialogue-bd .drawingregion {
|
||||
position: inherit;
|
||||
}
|
||||
.assignfeedback_editpdf_widget .drawingregion {
|
||||
border: 1px solid #ccc;
|
||||
left: 1em;
|
||||
right: 1em;
|
||||
top: 52px;
|
||||
bottom: 0px;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
width: 842px; /* A4 portrait should not need a horizontal scrollbar */
|
||||
}
|
||||
|
||||
.assignfeedback_editpdf_widget {
|
||||
@@ -32,18 +42,25 @@
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
min-height: 50px;
|
||||
height: 52px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.moodle-dialogue-base .moodle-dialogue.assignfeedback_editpdf_widget .moodle-dialogue-bd {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.assignfeedback_editpdf_unsavedchanges.haschanges{
|
||||
display: block;
|
||||
.assignfeedback_editpdf_widget .assignfeedback_editpdf_unsavedchanges.haschanges{
|
||||
display: inline-block;
|
||||
}
|
||||
.assignfeedback_editpdf_unsavedchanges {
|
||||
.assignfeedback_editpdf_widget .assignfeedback_editpdf_unsavedchanges {
|
||||
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-commentsearch-hidden,
|
||||
@@ -290,3 +307,31 @@ ul.assignfeedback_editpdf_menu {
|
||||
overflow: hidden;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,9 @@ Feature: In an assignment, teacher can annotate PDF files during grading
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 "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 click on ".navigate-next-button" "css_element"
|
||||
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 ".commentcolourbutton" "css_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 click on "Close" "button"
|
||||
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
|
||||
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 follow "Course 1"
|
||||
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 "Grade" "link" in the "Student 2" "table_row"
|
||||
And I follow "Launch PDF editor..."
|
||||
And I change window size to "large"
|
||||
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 click on ".linebutton" "css_element"
|
||||
And I draw on the pdf
|
||||
And I press "Save changes"
|
||||
And I should see "The grade changes were saved"
|
||||
And I press "Continue"
|
||||
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 follow "Test assignment name"
|
||||
And I follow "View all submissions"
|
||||
And I should see "View annotated PDF..." in the "student2@example.com" "table_row"
|
||||
|
||||
@@ -49,4 +49,33 @@ class behat_assignfeedback_editpdf extends behat_base {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 follow "Course 1"
|
||||
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 follow "Launch PDF editor..."
|
||||
And I change window size to "large"
|
||||
And I click on ".navigate-next-button" "css_element"
|
||||
And I click on ".stampbutton" "css_element"
|
||||
And I click on ".drawingcanvas" "css_element"
|
||||
And I change window size to "medium"
|
||||
And I draw on the pdf
|
||||
And I wait until the page is ready
|
||||
And I click on "Close" "button"
|
||||
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 in as "student1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
When I follow "View annotated PDF..."
|
||||
And I change window size to "large"
|
||||
Then I should see "Annotate PDF"
|
||||
And I change window size to "medium"
|
||||
And I wait until the page is ready
|
||||
And I click on "Close" "button"
|
||||
And I log out
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2015111600;
|
||||
$plugin->version = 2016021601;
|
||||
$plugin->requires = 2015111000;
|
||||
$plugin->component = 'assignfeedback_editpdf';
|
||||
|
||||
|
||||
+90
-15
@@ -42,6 +42,7 @@ var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
|
||||
ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton',
|
||||
DELETEANNOTATIONBUTTON : '.deleteannotationbutton',
|
||||
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
|
||||
UNSAVEDCHANGESINPUT : 'input[name="assignfeedback_editpdf_haschanges"]',
|
||||
STAMPSBUTTON : '.currentstampbutton',
|
||||
DIALOGUE : '.' + CSS.DIALOGUE
|
||||
},
|
||||
@@ -3097,6 +3098,15 @@ EDITOR.prototype = {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -3258,11 +3268,24 @@ EDITOR.prototype = {
|
||||
link.on('click', this.link_handler, this);
|
||||
link.on('key', this.link_handler, 'down:13', this);
|
||||
|
||||
this.currentedit.start = false;
|
||||
this.currentedit.end = false;
|
||||
if (!this.get('readonly')) {
|
||||
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
|
||||
}
|
||||
// We call the amd module to see if we can take control of the review panel.
|
||||
require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
|
||||
var panelManager = new ReviewPanelManager();
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @method link_handler
|
||||
@@ -3496,14 +3549,18 @@ EDITOR.prototype = {
|
||||
try {
|
||||
data = Y.JSON.parse(responsetext);
|
||||
if (data.error || !data.pagecount) {
|
||||
this.dialogue.hide();
|
||||
if (this.dialogue) {
|
||||
this.dialogue.hide();
|
||||
}
|
||||
// Display alert dialogue.
|
||||
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
|
||||
error.show();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.dialogue.hide();
|
||||
if (this.dialogue) {
|
||||
this.dialogue.hide();
|
||||
}
|
||||
// Display alert dialogue.
|
||||
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
|
||||
error.show();
|
||||
@@ -3734,7 +3791,11 @@ EDITOR.prototype = {
|
||||
* @method get_dialogue_element
|
||||
*/
|
||||
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() {
|
||||
var drawingregion, drawregionheight;
|
||||
|
||||
if (!this.dialogue.get('visible')) {
|
||||
return;
|
||||
if (this.dialogue) {
|
||||
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.
|
||||
drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
|
||||
@@ -3926,7 +3989,9 @@ EDITOR.prototype = {
|
||||
drawregionheight = 100;
|
||||
}
|
||||
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
|
||||
drawingregion.setStyle('maxHeight', drawregionheight +'px');
|
||||
if (this.dialogue) {
|
||||
drawingregion.setStyle('maxHeight', drawregionheight +'px');
|
||||
}
|
||||
this.redraw();
|
||||
return true;
|
||||
},
|
||||
@@ -3985,8 +4050,16 @@ EDITOR.prototype = {
|
||||
if (jsondata.error) {
|
||||
return new M.core.ajaxException(jsondata);
|
||||
}
|
||||
Y.one('#' + this.get('linkid')).siblings(SELECTOR.UNSAVEDCHANGESDIV)
|
||||
.item(0).addClass('haschanges');
|
||||
Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
|
||||
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) {
|
||||
return new M.core.exception(e);
|
||||
}
|
||||
@@ -4229,7 +4302,8 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
|
||||
* @param {Object} 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",
|
||||
"event-move",
|
||||
"event-resize",
|
||||
"transition",
|
||||
"querystring-stringify-simple",
|
||||
"moodle-core-notification-dialog",
|
||||
"moodle-core-notification-exception",
|
||||
|
||||
+8
-8
File diff suppressed because one or more lines are too long
+90
-15
@@ -42,6 +42,7 @@ var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
|
||||
ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton',
|
||||
DELETEANNOTATIONBUTTON : '.deleteannotationbutton',
|
||||
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
|
||||
UNSAVEDCHANGESINPUT : 'input[name="assignfeedback_editpdf_haschanges"]',
|
||||
STAMPSBUTTON : '.currentstampbutton',
|
||||
DIALOGUE : '.' + CSS.DIALOGUE
|
||||
},
|
||||
@@ -3097,6 +3098,15 @@ EDITOR.prototype = {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -3258,11 +3268,24 @@ EDITOR.prototype = {
|
||||
link.on('click', this.link_handler, this);
|
||||
link.on('key', this.link_handler, 'down:13', this);
|
||||
|
||||
this.currentedit.start = false;
|
||||
this.currentedit.end = false;
|
||||
if (!this.get('readonly')) {
|
||||
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
|
||||
}
|
||||
// We call the amd module to see if we can take control of the review panel.
|
||||
require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
|
||||
var panelManager = new ReviewPanelManager();
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @method link_handler
|
||||
@@ -3496,14 +3549,18 @@ EDITOR.prototype = {
|
||||
try {
|
||||
data = Y.JSON.parse(responsetext);
|
||||
if (data.error || !data.pagecount) {
|
||||
this.dialogue.hide();
|
||||
if (this.dialogue) {
|
||||
this.dialogue.hide();
|
||||
}
|
||||
// Display alert dialogue.
|
||||
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
|
||||
error.show();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.dialogue.hide();
|
||||
if (this.dialogue) {
|
||||
this.dialogue.hide();
|
||||
}
|
||||
// Display alert dialogue.
|
||||
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
|
||||
error.show();
|
||||
@@ -3734,7 +3791,11 @@ EDITOR.prototype = {
|
||||
* @method get_dialogue_element
|
||||
*/
|
||||
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() {
|
||||
var drawingregion, drawregionheight;
|
||||
|
||||
if (!this.dialogue.get('visible')) {
|
||||
return;
|
||||
if (this.dialogue) {
|
||||
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.
|
||||
drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
|
||||
@@ -3926,7 +3989,9 @@ EDITOR.prototype = {
|
||||
drawregionheight = 100;
|
||||
}
|
||||
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
|
||||
drawingregion.setStyle('maxHeight', drawregionheight +'px');
|
||||
if (this.dialogue) {
|
||||
drawingregion.setStyle('maxHeight', drawregionheight +'px');
|
||||
}
|
||||
this.redraw();
|
||||
return true;
|
||||
},
|
||||
@@ -3985,8 +4050,16 @@ EDITOR.prototype = {
|
||||
if (jsondata.error) {
|
||||
return new M.core.ajaxException(jsondata);
|
||||
}
|
||||
Y.one('#' + this.get('linkid')).siblings(SELECTOR.UNSAVEDCHANGESDIV)
|
||||
.item(0).addClass('haschanges');
|
||||
Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
|
||||
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) {
|
||||
return new M.core.exception(e);
|
||||
}
|
||||
@@ -4229,7 +4302,8 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
|
||||
* @param {Object} 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",
|
||||
"event-move",
|
||||
"event-resize",
|
||||
"transition",
|
||||
"querystring-stringify-simple",
|
||||
"moodle-core-notification-dialog",
|
||||
"moodle-core-notification-exception",
|
||||
|
||||
+88
-15
@@ -42,6 +42,15 @@ EDITOR.prototype = {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -203,11 +212,24 @@ EDITOR.prototype = {
|
||||
link.on('click', this.link_handler, this);
|
||||
link.on('key', this.link_handler, 'down:13', this);
|
||||
|
||||
this.currentedit.start = false;
|
||||
this.currentedit.end = false;
|
||||
if (!this.get('readonly')) {
|
||||
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
|
||||
}
|
||||
// We call the amd module to see if we can take control of the review panel.
|
||||
require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
|
||||
var panelManager = new ReviewPanelManager();
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @method link_handler
|
||||
@@ -441,14 +493,18 @@ EDITOR.prototype = {
|
||||
try {
|
||||
data = Y.JSON.parse(responsetext);
|
||||
if (data.error || !data.pagecount) {
|
||||
this.dialogue.hide();
|
||||
if (this.dialogue) {
|
||||
this.dialogue.hide();
|
||||
}
|
||||
// Display alert dialogue.
|
||||
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
|
||||
error.show();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.dialogue.hide();
|
||||
if (this.dialogue) {
|
||||
this.dialogue.hide();
|
||||
}
|
||||
// Display alert dialogue.
|
||||
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
|
||||
error.show();
|
||||
@@ -679,7 +735,11 @@ EDITOR.prototype = {
|
||||
* @method get_dialogue_element
|
||||
*/
|
||||
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() {
|
||||
var drawingregion, drawregionheight;
|
||||
|
||||
if (!this.dialogue.get('visible')) {
|
||||
return;
|
||||
if (this.dialogue) {
|
||||
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.
|
||||
drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
|
||||
@@ -871,7 +933,9 @@ EDITOR.prototype = {
|
||||
drawregionheight = 100;
|
||||
}
|
||||
drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
|
||||
drawingregion.setStyle('maxHeight', drawregionheight +'px');
|
||||
if (this.dialogue) {
|
||||
drawingregion.setStyle('maxHeight', drawregionheight +'px');
|
||||
}
|
||||
this.redraw();
|
||||
return true;
|
||||
},
|
||||
@@ -930,8 +994,16 @@ EDITOR.prototype = {
|
||||
if (jsondata.error) {
|
||||
return new M.core.ajaxException(jsondata);
|
||||
}
|
||||
Y.one('#' + this.get('linkid')).siblings(SELECTOR.UNSAVEDCHANGESDIV)
|
||||
.item(0).addClass('haschanges');
|
||||
Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
|
||||
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) {
|
||||
return new M.core.exception(e);
|
||||
}
|
||||
@@ -1174,5 +1246,6 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
|
||||
* @param {Object} 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;
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
|
||||
ANNOTATIONCOLOURBUTTON : '.annotationcolourbutton',
|
||||
DELETEANNOTATIONBUTTON : '.deleteannotationbutton',
|
||||
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
|
||||
UNSAVEDCHANGESINPUT : 'input[name="assignfeedback_editpdf_haschanges"]',
|
||||
STAMPSBUTTON : '.currentstampbutton',
|
||||
DIALOGUE : '.' + CSS.DIALOGUE
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"json",
|
||||
"event-move",
|
||||
"event-resize",
|
||||
"transition",
|
||||
"querystring-stringify-simple",
|
||||
"moodle-core-notification-dialog",
|
||||
"moodle-core-notification-exception",
|
||||
|
||||
@@ -167,6 +167,15 @@ abstract class assign_feedback_plugin extends assign_plugin {
|
||||
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
|
||||
*
|
||||
|
||||
@@ -833,16 +833,12 @@ class assign_grading_table extends table_sql implements renderable {
|
||||
$gradingdisabled = $this->assignment->grading_disabled($row->id);
|
||||
|
||||
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,
|
||||
'rownum'=>$this->rownum,
|
||||
'action' => 'grade',
|
||||
'useridlistid' => $this->assignment->get_useridlist_key_id());
|
||||
'rownum' => 0,
|
||||
'action' => 'grader',
|
||||
'userid' => $row->userid);
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ $string['batchsetmarkingworkflowstateforusers'] = 'Set marking workflow state fo
|
||||
$string['blindmarking'] = 'Blind marking';
|
||||
$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['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['choosegradingaction'] = 'Grading action';
|
||||
$string['choosemarker'] = 'Choose...';
|
||||
@@ -120,6 +122,7 @@ $string['currentgrade'] = 'Current grade in gradebook';
|
||||
$string['currentattempt'] = 'This is attempt {$a}.';
|
||||
$string['currentattemptof'] = 'This is attempt {$a->attemptnumber} ( {$a->maxattempts} attempts allowed ).';
|
||||
$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['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.';
|
||||
@@ -131,6 +134,7 @@ $string['description'] = 'Description';
|
||||
$string['downloadall'] = 'Download all submissions';
|
||||
$string['download all submissions'] = 'Download all submissions in a zip file.';
|
||||
$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['duedateno'] = 'No due date';
|
||||
$string['submissionempty'] = 'Nothing was submitted';
|
||||
@@ -185,6 +189,7 @@ $string['gradersubmissionupdatedsmall'] = '{$a->username} has updated their subm
|
||||
$string['gradeuser'] = 'Grade {$a}';
|
||||
$string['grantextension'] = 'Grant extension';
|
||||
$string['grantextensionforusers'] = 'Grant extension for {$a} students';
|
||||
$string['groupsubmissionsettings'] = 'Group submission settings';
|
||||
$string['enabled'] = 'Enabled';
|
||||
$string['errornosubmissions'] = 'There are no submissions to download';
|
||||
$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['feedbackplugin'] = 'Feedback plugin';
|
||||
$string['feedbacksettings'] = 'Feedback settings';
|
||||
$string['feedbacktypes'] = 'Feedback types';
|
||||
$string['filesubmissions'] = 'File submissions';
|
||||
$string['filter'] = '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['grading'] = 'Grading';
|
||||
$string['gradingchangessaved'] = 'The grade changes were saved';
|
||||
$string['gradechangessaveddetail'] = 'The changes to the grade and feedback were saved';
|
||||
$string['gradingmethodpreview'] = 'Grading criteria';
|
||||
$string['gradingoptions'] = 'Options';
|
||||
$string['gradingstatus'] = 'Grading status';
|
||||
@@ -239,6 +246,7 @@ $string['lastmodifiedsubmission'] = 'Last modified (submission)';
|
||||
$string['lastmodifiedgrade'] = 'Last modified (grade)';
|
||||
$string['latesubmissions'] = 'Late submissions';
|
||||
$string['latesubmissionsaccepted'] = 'Allowed until {$a}';
|
||||
$string['loading'] = 'Loading...';
|
||||
$string['locksubmissionforstudent'] = 'Prevent any more submissions for student: (id={$a->id}, fullname={$a->fullname}).';
|
||||
$string['locksubmissions'] = 'Lock submissions';
|
||||
$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['newsubmissions'] = 'Assignments submitted';
|
||||
$string['noattempt'] = 'No attempt';
|
||||
$string['nofilters'] = 'No filters';
|
||||
$string['nofiles'] = 'No files. ';
|
||||
$string['nograde'] = 'No grade. ';
|
||||
$string['nolatesubmissions'] = 'No late submissions accepted. ';
|
||||
@@ -299,6 +308,7 @@ $string['notgradedyet'] = 'Not graded yet';
|
||||
$string['notsubmittedyet'] = 'Not submitted yet';
|
||||
$string['notifications'] = 'Notifications';
|
||||
$string['nousersselected'] = 'No users selected';
|
||||
$string['nousers'] = 'No users';
|
||||
$string['numberofdraftsubmissions'] = 'Drafts';
|
||||
$string['numberofparticipants'] = 'Participants';
|
||||
$string['numberofsubmittedassignments'] = 'Submitted';
|
||||
@@ -311,6 +321,7 @@ $string['overdue'] = '<font color="red">Assignment is overdue by: {$a}</font>';
|
||||
$string['outlinegrade'] = 'Grade: {$a}';
|
||||
$string['page-mod-assign-x'] = 'Any assignment module page';
|
||||
$string['page-mod-assign-view'] = 'Assignment module main and submission page';
|
||||
$string['paramtimeremaining'] = '{$a} remaining';
|
||||
$string['participant'] = 'Participant';
|
||||
$string['pluginadministration'] = 'Assignment administration';
|
||||
$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['reverttodraftshort'] = 'Revert the submission to draft';
|
||||
$string['reviewed'] = 'Reviewed';
|
||||
$string['saveallquickgradingchanges'] = 'Save all quick grading changes';
|
||||
$string['saveandcontinue'] = 'Save and continue';
|
||||
$string['savechanges'] = 'Save changes';
|
||||
$string['savegradingresult'] = 'Grade';
|
||||
$string['saveallquickgradingchanges'] = 'Save all quick grading changes';
|
||||
$string['savenext'] = 'Save and show next';
|
||||
$string['savingchanges'] = 'Saving changes...';
|
||||
$string['scale'] = 'Scale';
|
||||
$string['search:activity'] = 'Assignment activities';
|
||||
$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_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['submissionlog'] = 'Student: {$a->fullname}, Status: {$a->status}';
|
||||
$string['submissionnotcopiedinvalidstatus'] = 'The submission was not copied because it has been edited since it was reopened.';
|
||||
$string['submissionnoteditable'] = 'Student cannot edit this submission';
|
||||
$string['submissionnotready'] = 'This assignment is not ready to submit:';
|
||||
@@ -419,6 +433,7 @@ $string['submissionstatus'] = 'Submission status';
|
||||
$string['submissionstatus_submitted'] = 'Submitted for grading';
|
||||
$string['submissionsummary'] = '{$a->status}. Last modified on {$a->timemodified}';
|
||||
$string['submissionteam'] = 'Group';
|
||||
$string['submissiontypes'] = 'Submission types';
|
||||
$string['submission'] = 'Submission';
|
||||
$string['submitaction'] = 'Submit';
|
||||
$string['submitforgrading'] = 'Submit for grading';
|
||||
@@ -439,11 +454,15 @@ $string['teamsubmissiongroupingid_help'] = 'This is the grouping that the assign
|
||||
$string['textinstructions'] = 'Assignment instructions';
|
||||
$string['timemodified'] = 'Last modified';
|
||||
$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['unlocksubmissionforstudent'] = 'Allow submissions for student: (id={$a->id}, fullname={$a->fullname}).';
|
||||
$string['unlocksubmissions'] = 'Unlock submissions';
|
||||
$string['unlimitedattempts'] = 'Unlimited';
|
||||
$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['updatetable'] = 'Save and update table';
|
||||
$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['usergrade'] = 'User grade';
|
||||
$string['validmarkingworkflowstates'] = 'Valid marking workflow states';
|
||||
$string['viewadifferentattempt'] = 'View a different attempt';
|
||||
$string['viewbatchsetmarkingworkflowstate'] = 'View batch set marking workflow state page.';
|
||||
$string['viewbatchmarkingallocation'] = 'View batch set marking allocation page.';
|
||||
$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['viewgradebook'] = 'View gradebook';
|
||||
$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['viewownsubmissionstatus'] = 'View own submission status page.';
|
||||
$string['viewsubmissionforuser'] = 'View submission for user: {$a}';
|
||||
@@ -469,7 +489,4 @@ $string['viewsummary'] = 'View summary';
|
||||
$string['viewsubmissiongradingtable'] = 'View submission grading table.';
|
||||
$string['viewrevealidentitiesconfirm'] = 'View reveal student identities confirmation page.';
|
||||
$string['workflowfilter'] = 'Workflow filter';
|
||||
$string['submissiontypes'] = 'Submission types';
|
||||
$string['feedbacktypes'] = 'Feedback types';
|
||||
$string['groupsubmissionsettings'] = 'Group submission settings';
|
||||
$string['submissionlog'] = 'Student: {$a->fullname}, Status: {$a->status}';
|
||||
$string['xofy'] = '{$a->x} of {$a->y}';
|
||||
|
||||
@@ -1474,3 +1474,36 @@ function assign_pluginfile($course,
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
+330
-46
@@ -77,6 +77,8 @@ require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
|
||||
require_once($CFG->libdir . '/eventslib.php');
|
||||
require_once($CFG->libdir . '/portfolio/caller.php');
|
||||
|
||||
use \mod_assign\output\grading_app;
|
||||
|
||||
/**
|
||||
* Standard base class for mod_assign (assignment types).
|
||||
*
|
||||
@@ -203,7 +205,7 @@ class assign {
|
||||
public function register_return_link($action, $params) {
|
||||
global $PAGE;
|
||||
$params['action'] = $action;
|
||||
$currenturl = $PAGE->url;
|
||||
$currenturl = new moodle_url('/mod/assign/view.php');
|
||||
|
||||
$currenturl->params($params);
|
||||
$PAGE->set_url($currenturl);
|
||||
@@ -406,9 +408,10 @@ class assign {
|
||||
* the settings for the assignment and the status of the assignment.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function view($action='') {
|
||||
public function view($action='', $args = array()) {
|
||||
global $PAGE;
|
||||
|
||||
$o = '';
|
||||
@@ -548,6 +551,8 @@ class assign {
|
||||
} else if ($action == 'quickgradingresult') {
|
||||
$mform = null;
|
||||
$o .= $this->view_quickgrading_result($message);
|
||||
} else if ($action == 'gradingpanel') {
|
||||
$o .= $this->view_single_grading_panel($args);
|
||||
} else if ($action == 'grade') {
|
||||
$o .= $this->view_single_grade_page($mform);
|
||||
} else if ($action == 'viewpluginassignfeedback') {
|
||||
@@ -556,6 +561,8 @@ class assign {
|
||||
$o .= $this->view_plugin_content('assignsubmission');
|
||||
} else if ($action == 'editsubmission') {
|
||||
$o .= $this->view_edit_submission_page($mform, $notices);
|
||||
} else if ($action == 'grader') {
|
||||
$o .= $this->view_grader();
|
||||
} else if ($action == 'grading') {
|
||||
$o .= $this->view_grading_page();
|
||||
} else if ($action == 'downloadall') {
|
||||
@@ -1413,6 +1420,84 @@ 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
|
||||
* @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.
|
||||
* 0 for no group.
|
||||
@@ -1429,7 +1514,11 @@ class assign {
|
||||
|
||||
$key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
|
||||
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());
|
||||
|
||||
$cm = $this->get_course_module();
|
||||
@@ -2836,7 +2925,7 @@ class assign {
|
||||
if ($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;
|
||||
}
|
||||
|
||||
@@ -3047,6 +3136,166 @@ class assign {
|
||||
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.
|
||||
*
|
||||
@@ -3195,7 +3444,7 @@ class assign {
|
||||
// Warning if required.
|
||||
$allsubmissions = $this->get_all_submissions($userid);
|
||||
|
||||
if ($attemptnumber != -1) {
|
||||
if ($attemptnumber != -1 && ($attemptnumber + 1) != count($allsubmissions)) {
|
||||
$params = array('attemptnumber'=>$attemptnumber + 1,
|
||||
'totalattempts'=>count($allsubmissions));
|
||||
$message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
|
||||
@@ -3485,6 +3734,37 @@ class assign {
|
||||
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.
|
||||
*
|
||||
@@ -3566,10 +3846,10 @@ class assign {
|
||||
public function fullname($user) {
|
||||
if ($this->is_blind_marking()) {
|
||||
$hasviewblind = has_capability('mod/assign:viewblinddetails', $this->get_context());
|
||||
$uniqueid = $this->get_uniqueid_for_user($user->id);
|
||||
if ($hasviewblind) {
|
||||
return fullname($user);
|
||||
return get_string('participant', 'assign') . ' ' . $uniqueid . ' (' . fullname($user) . ')';
|
||||
} else {
|
||||
$uniqueid = $this->get_uniqueid_for_user($user->id);
|
||||
return get_string('participant', 'assign') . ' ' . $uniqueid;
|
||||
}
|
||||
} else {
|
||||
@@ -3666,15 +3946,10 @@ class assign {
|
||||
public function can_view_group_submission($groupid) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -3693,12 +3968,12 @@ class assign {
|
||||
if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
|
||||
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)) {
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
@@ -6226,11 +6501,12 @@ class assign {
|
||||
global $USER, $CFG, $SESSION;
|
||||
$settings = $this->get_instance();
|
||||
|
||||
$rownum = $params['rownum'];
|
||||
$last = $params['last'];
|
||||
$useridlistid = $params['useridlistid'];
|
||||
$userid = $params['userid'];
|
||||
$attemptnumber = $params['attemptnumber'];
|
||||
$rownum = isset($params['rownum']) ? $params['rownum'] : 0;
|
||||
$last = isset($params['last']) ? $params['last'] : true;
|
||||
$useridlistid = isset($params['useridlistid']) ? $params['useridlistid'] : 0;
|
||||
$userid = isset($params['userid']) ? $params['userid'] : 0;
|
||||
$attemptnumber = isset($params['attemptnumber']) ? $params['attemptnumber'] : 0;
|
||||
$gradingpanel = !empty($params['gradingpanel']);
|
||||
if (!$userid) {
|
||||
$useridlistkey = $this->get_useridlist_key($useridlistid);
|
||||
if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
|
||||
@@ -6373,7 +6649,7 @@ class assign {
|
||||
$mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE);
|
||||
$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);
|
||||
|
||||
if (count($useridlist) > 1) {
|
||||
@@ -6431,7 +6707,12 @@ class assign {
|
||||
$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.
|
||||
$modinfo = get_fast_modinfo($settings->course, $userid);
|
||||
$cm = $modinfo->get_cm($this->get_course_module()->id);
|
||||
@@ -6451,29 +6732,32 @@ class assign {
|
||||
$mform->addElement('hidden', 'action', 'submitgrade');
|
||||
$mform->setType('action', PARAM_ALPHA);
|
||||
|
||||
$buttonarray=array();
|
||||
$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 (!$gradingpanel) {
|
||||
|
||||
if ($rownum > 0) {
|
||||
$name = get_string('previous', 'assign');
|
||||
$buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', $name);
|
||||
}
|
||||
$buttonarray = array();
|
||||
$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 (!$last) {
|
||||
$name = get_string('nosavebutnext', 'assign');
|
||||
$buttonarray[] = $mform->createElement('submit', 'nosaveandnext', $name);
|
||||
}
|
||||
if (!empty($buttonarray)) {
|
||||
$mform->addGroup($buttonarray, 'navar', '', array(' '), false);
|
||||
if ($rownum > 0) {
|
||||
$name = get_string('previous', 'assign');
|
||||
$buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', $name);
|
||||
}
|
||||
|
||||
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.
|
||||
$mform->setDisableShortforms();
|
||||
@@ -7057,7 +7341,7 @@ class assign {
|
||||
} else {
|
||||
$submission = $this->get_user_submission($userid, false, $data->attemptnumber);
|
||||
}
|
||||
if ($instance->teamsubmission && $data->applytoall) {
|
||||
if ($instance->teamsubmission && !empty($data->applytoall)) {
|
||||
$groupid = 0;
|
||||
if ($this->get_submission_group($userid)) {
|
||||
$group = $this->get_submission_group($userid);
|
||||
|
||||
@@ -488,6 +488,15 @@ class assign_submission_status implements renderable {
|
||||
$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.
|
||||
@@ -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
|
||||
* @package mod_assign
|
||||
|
||||
+218
-4
@@ -26,6 +26,8 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -333,14 +335,19 @@ class mod_assign_renderer extends plugin_renderer_base {
|
||||
$o .= $this->output->box_end();
|
||||
|
||||
// Link to the grading page.
|
||||
$o .= $this->output->container_start('submissionlinks');
|
||||
$urlparams = array('id' => $summary->coursemoduleid, 'action'=>'grading');
|
||||
$o .= '<center>';
|
||||
$o .= $this->output->container_start('submissionlinks btn-group');
|
||||
$urlparams = array('id' => $summary->coursemoduleid, 'action' => 'grading');
|
||||
$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();
|
||||
|
||||
// Close the container and insert a spacer.
|
||||
$o .= $this->output->container_end();
|
||||
$o .= '</center>';
|
||||
|
||||
return $o;
|
||||
}
|
||||
@@ -417,6 +424,189 @@ class mod_assign_renderer extends plugin_renderer_base {
|
||||
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.
|
||||
*
|
||||
@@ -772,6 +962,21 @@ class mod_assign_renderer extends plugin_renderer_base {
|
||||
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
|
||||
*
|
||||
@@ -797,7 +1002,7 @@ class mod_assign_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
foreach ($history->submissions as $i => $submission) {
|
||||
@@ -1248,5 +1453,14 @@ class mod_assign_renderer extends plugin_renderer_base {
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defer to template..
|
||||
*
|
||||
* @param grading_app $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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+695
-1
@@ -307,4 +307,698 @@
|
||||
}
|
||||
.path-mod-assign .editsubmissionform label[for="id_submissionstatement"] {
|
||||
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 **/
|
||||
|
||||
@@ -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}}
|
||||
@@ -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}}
|
||||
@@ -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>
|
||||
@@ -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}}
|
||||
@@ -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>
|
||||
@@ -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}}
|
||||
@@ -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>
|
||||
@@ -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": "[email protected], 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>
|
||||
@@ -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}}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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}}
|
||||
@@ -0,0 +1 @@
|
||||
<a href="#" data-region="popout-button">{{#pix}}e/fullscreen,core,{{#str}}togglezoom, mod_assign{{/str}}{{/pix}}</a>
|
||||
@@ -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 }}
|
||||
@@ -310,11 +310,15 @@ class testable_assign extends assign {
|
||||
}
|
||||
|
||||
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);
|
||||
return parent::view_batch_set_workflow_state($mform);
|
||||
}
|
||||
|
||||
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);
|
||||
return parent::view_batch_markingallocation($mform);
|
||||
}
|
||||
|
||||
@@ -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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Allow another attempt | 1 |
|
||||
And I press "Save changes"
|
||||
And I press "Ok"
|
||||
And I click on "Edit settings" "link"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
And I should see "I'm the student first submission"
|
||||
|
||||
@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 follow "Course 1"
|
||||
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"
|
||||
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"
|
||||
@@ -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 follow "Course 1"
|
||||
# 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 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 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:
|
||||
# | Allow another attempt | 1 |
|
||||
# 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 follow "Course 1"
|
||||
# And I follow "Test assignment name"
|
||||
# And I follow "View/grade all submissions"
|
||||
# And I click on "Grade Student 4" "link" in the "Student 1" "table_row"
|
||||
# And I follow "View all submissions"
|
||||
# And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
#And I should see "This is attempt 2 (3 attempts allowed)"
|
||||
|
||||
@@ -40,14 +40,17 @@ Feature: In an assignment, teachers can edit a students submission inline
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Grade out of 100 | 50 |
|
||||
| Feedback comments | I'm the teacher feedback |
|
||||
And I upload "lib/tests/fixtures/empty.txt" file to "Feedback files" filemanager
|
||||
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"
|
||||
And I should see "Submitted for grading" in the "Student 1" "table_row"
|
||||
And I should see "Graded" in the "Student 1" "table_row"
|
||||
|
||||
@@ -28,8 +28,8 @@ Feature: Check that the assignment grade can not be input in a wrong format.
|
||||
| Description | Test assignment description |
|
||||
| Use marking workflow | Yes |
|
||||
When I follow "Test assignment name"
|
||||
Then I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
Then I follow "View all submissions"
|
||||
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 press "Save changes"
|
||||
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 |
|
||||
| Use marking workflow | Yes |
|
||||
When I follow "Test assignment name"
|
||||
Then I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
Then I follow "View all submissions"
|
||||
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 press "Save changes"
|
||||
And I should see "The grade provided could not be understood: 50..6"
|
||||
|
||||
@@ -28,11 +28,14 @@ Feature: Check that the assignment grade can be updated correctly
|
||||
| Description | Test assignment description |
|
||||
| Use marking workflow | Yes |
|
||||
When I follow "Test assignment name"
|
||||
Then I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
Then I follow "View all submissions"
|
||||
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 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"
|
||||
|
||||
@javascript
|
||||
@@ -61,9 +64,12 @@ Feature: Check that the assignment grade can be updated correctly
|
||||
| Students submit in groups | Yes |
|
||||
| Group mode | No groups |
|
||||
When I follow "Test assignment name"
|
||||
Then I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
Then I follow "View all submissions"
|
||||
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 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"
|
||||
|
||||
@@ -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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 2" "link" in the "Student 2" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 2" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Grade | 49 |
|
||||
| Feedback comments | I'm the teacher first feedback |
|
||||
| Allow another attempt | Yes |
|
||||
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 in as "student2"
|
||||
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"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 2" "link" in the "Student 2" "table_row"
|
||||
And I click on ".mod-assign-history-link" "css_element"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 2" "table_row"
|
||||
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:
|
||||
| Grade | 50 |
|
||||
| Feedback comments | I'm the teacher second feedback |
|
||||
And I press "Save changes"
|
||||
And I click on "Ok" "button"
|
||||
And I click on "Edit settings" "link"
|
||||
And I log out
|
||||
Then I log in as "student2"
|
||||
And I follow "Course 1"
|
||||
|
||||
@@ -32,15 +32,17 @@ Feature: In an assignment, teachers can filter displayed submissions by assigned
|
||||
| Use marking workflow | Yes |
|
||||
| Use marking allocation | Yes |
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
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
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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"
|
||||
Then I should see "Student 1"
|
||||
And I should not see "Student 2"
|
||||
|
||||
@@ -50,14 +50,17 @@ Feature: View the grading status of an assignment
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 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 "Marking workflow state" to "In review"
|
||||
And I set the field "Feedback comments" to "Great job! Lol, not really."
|
||||
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 log out
|
||||
# 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 follow "Course 1"
|
||||
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 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 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 log out
|
||||
# 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 follow "Course 1"
|
||||
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 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 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"
|
||||
# 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"
|
||||
@@ -137,13 +146,16 @@ Feature: View the grading status of an assignment
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 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 "Feedback comments" to "Great job! Lol, not really."
|
||||
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 log out
|
||||
# View the grading status as a student.
|
||||
|
||||
@@ -35,7 +35,7 @@ Feature: Grant an extension to an offline student
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 follow "Grant extension"
|
||||
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 follow "Course 1"
|
||||
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 "operation" to "Grant extension"
|
||||
And I click on "Go" "button" confirming the dialogue
|
||||
|
||||
@@ -35,7 +35,7 @@ Feature: Group assignment submissions
|
||||
| Students submit in groups | Yes |
|
||||
| Group mode | No groups |
|
||||
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
|
||||
And "//tr[contains(., 'Student 1')][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 follow "Course 1"
|
||||
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 1')][contains(., 'Group 1')]" "xpath_element" should exist
|
||||
And I should not see "Student 2"
|
||||
@@ -113,7 +113,7 @@ Feature: Group assignment submissions
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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"
|
||||
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"
|
||||
@@ -130,7 +130,7 @@ Feature: Group assignment submissions
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 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"
|
||||
|
||||
@@ -62,12 +62,15 @@ Feature: Outcome grading
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "img[alt='Grade Student 0']" "css_element"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 0" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Outcome Test: | Excellent |
|
||||
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"
|
||||
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"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "img[alt='Grade Student 0']" "css_element"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 0" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Outcome Test: | Excellent |
|
||||
| Apply grades and feedback to entire group | Yes |
|
||||
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"
|
||||
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 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:
|
||||
| Outcome Test: | Disappointing |
|
||||
| Apply grades and feedback to entire group | No |
|
||||
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: Disappointing" in the "Student 1" "table_row"
|
||||
And I should not see "Outcome Test: Disappointing" in the "Student 0" "table_row"
|
||||
|
||||
@@ -45,7 +45,7 @@ Feature: Prevent or allow assignment submission changes
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 follow "Prevent submission changes"
|
||||
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 follow "Course 1"
|
||||
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 follow "Allow submission changes"
|
||||
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 follow "Course 1"
|
||||
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 click on "Go" "button" confirming the dialogue
|
||||
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 follow "Course 1"
|
||||
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 "id_operation" to "Unlock submissions"
|
||||
And I click on "Go" "button" confirming the dialogue
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@mod @mod_assign
|
||||
@mod @mod_assign @javascript
|
||||
Feature: In an assignment, teachers grade multiple students on one page
|
||||
In order to quickly give students grades and feedback
|
||||
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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
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"
|
||||
Then I should see "1" in the "Needs grading" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Grade multiple students on one page
|
||||
Given the following "courses" exist:
|
||||
| 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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Grade out of 100 | 50.0 |
|
||||
| M8d skillZ! | 1337 |
|
||||
| Feedback comments | I'm the teacher first feedback |
|
||||
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"
|
||||
And I set the field "User grade" to "60.0"
|
||||
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 follow "Course 1"
|
||||
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 Full name" "link"
|
||||
And I click on "Hide Email address" "link"
|
||||
|
||||
@@ -41,7 +41,7 @@ Feature: Submissions are unlocked when a new attempt is given
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
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 follow "Prevent submission changes"
|
||||
And I should see "Submission changes not allowed"
|
||||
@@ -76,11 +76,11 @@ Feature: Submissions are unlocked when a new attempt is given
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
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 follow "Prevent submission changes"
|
||||
And I should see "Submission changes not allowed"
|
||||
And I click on "Edit" "link" in the "Student 1" "table_row"
|
||||
And I follow "Allow another attempt"
|
||||
Then I should see "Reopened"
|
||||
And I should not see "Submission changes not allowed"
|
||||
And I should not see "Submission changes not allowed"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@mod @mod_assign
|
||||
@mod @mod_assign @bug
|
||||
Feature: Check that the assignment grade can be rescaled when the max grade is changed
|
||||
In order to ensure that the percentages are not affected by changes to the max grade
|
||||
As a teacher
|
||||
@@ -26,7 +26,7 @@ Feature: Check that the assignment grade can be rescaled when the max grade is c
|
||||
| Assignment name | Test assignment name |
|
||||
| Description | Test assignment description |
|
||||
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 set the field "Grade out of 100" to "40"
|
||||
And I press "Save changes"
|
||||
@@ -39,7 +39,7 @@ Feature: Check that the assignment grade can be rescaled when the max grade is c
|
||||
And I set the field "Maximum grade" to "80"
|
||||
And I set the field "Rescale existing grades" to "No"
|
||||
When I press "Save and display"
|
||||
And I follow "View/grade all submissions"
|
||||
And I follow "View all submissions"
|
||||
Then "Student 1" row "Grade" column of "generaltable" table should contain "40.00"
|
||||
|
||||
Scenario: Update the max grade for an assignment rescaling existing grades
|
||||
@@ -48,7 +48,7 @@ Feature: Check that the assignment grade can be rescaled when the max grade is c
|
||||
And I set the field "Maximum grade" to "50"
|
||||
And I set the field "Rescale existing grades" to "Yes"
|
||||
When I press "Save and display"
|
||||
And I follow "View/grade all submissions"
|
||||
And I follow "View all submissions"
|
||||
Then "Student 1" row "Grade" column of "generaltable" table should contain "20.00"
|
||||
|
||||
Scenario: A rescale existing grades is not needed when keeping max grade unchanged
|
||||
|
||||
@@ -44,27 +44,36 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
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 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 "Marking workflow state" to "In review"
|
||||
And I set the field "Feedback comments" to "Great job! Lol, not really."
|
||||
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"
|
||||
|
||||
@javascript
|
||||
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 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 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 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 set the field "Grading action" to "Reveal student identities"
|
||||
And I press "Continue"
|
||||
@@ -78,17 +87,23 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
|
||||
|
||||
@javascript
|
||||
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 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 set the field "Grading action" to "Reveal student identities"
|
||||
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 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 log out
|
||||
And I log in as "student1"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@mod @mod_assign
|
||||
@mod @mod_assign @javascript
|
||||
Feature: In an assignment, students can comment in their submissions
|
||||
In order to refine assignment submissions
|
||||
As a student
|
||||
@@ -17,7 +17,6 @@ Feature: In an assignment, students can comment in their submissions
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: Student comments an assignment submission
|
||||
Given the following "activities" exist:
|
||||
| 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 not see "First student comment"
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher can comment on an offline assignment
|
||||
Given the following "activities" exist:
|
||||
| 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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
When I set the following fields to these values:
|
||||
| Grade out of 100 | 50 |
|
||||
| Feedback comments | I'm the teacher feedback |
|
||||
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"
|
||||
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 follow "Course 1"
|
||||
And I follow "Test assignment name"
|
||||
And I follow "View/grade all submissions"
|
||||
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Grade out of 100 | 0 |
|
||||
And I press "Save changes"
|
||||
And I should see "The grade changes were saved"
|
||||
And I press "Continue"
|
||||
When I click on "Grade Student 1" "link" in the "Student 1" "table_row"
|
||||
And I should see "The changes to the grade and feedback were saved"
|
||||
And I press "Ok"
|
||||
And I set the following fields to these values:
|
||||
| Feedback comments | I'm the teacher feedback |
|
||||
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"
|
||||
|
||||
@@ -90,7 +90,7 @@ Feature: Submit assignment without group
|
||||
And I follow "Allow default group"
|
||||
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 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 2" "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 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 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 2" "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 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 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 2" "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 "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 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"
|
||||
|
||||
@@ -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.
|
||||
* This is an extra special media rule.
|
||||
|
||||
Reference in New Issue
Block a user