Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #16 from marinaglancy/wip-MDL-29483-rubric

MDL-29483: advanced grading methods support on assignment module, grading
  • Loading branch information...
commit d1bc8178326625d51fcd5d05559c3ed5a3877630 2 parents 671ec8f + fc05f22
@mudrd8mz mudrd8mz authored
View
381 grade/grading/form/lib.php
@@ -48,7 +48,10 @@
protected $areaid;
/** @var stdClass|false the definition structure */
- protected $definition;
+ protected $definition = false;
+
+ /** @var array graderange array of valid grades for this area. Use set_grade_range and get_grade_range to access this */
+ private $graderange = null;
/**
* Do not instantinate this directly, use {@link grading_manager::get_controller()}
@@ -107,7 +110,7 @@ public function get_areaid() {
* @return boolean
*/
public function is_form_defined() {
- return !empty($this->definition);
+ return ($this->definition !== false);
}
/**
@@ -174,10 +177,11 @@ public function extend_settings_navigation(settings_navigation $settingsnav, nav
/**
* Returns the grading form definition structure
*
+ * @param boolean $force whether to force loading from DB even if it was already loaded
* @return stdClass|false definition data or false if the form is not defined yet
*/
- public function get_definition() {
- if (is_null($this->definition)) {
+ public function get_definition($force = false) {
+ if ($this->definition === false || $force) {
$this->load_definition();
}
return $this->definition;
@@ -287,65 +291,92 @@ public function update_definition(stdClass $definition, $usermodified = null) {
}
/**
- * Makes sure there is a form instance for the given rater grading the given item
- *
- * Plugins will probably override/extend this and load additional data of how their
- * forms are filled in one complex query.
+ * Returns the ACTIVE instance for this definition for the specified $raterid and $itemid
+ * (if multiple raters are allowed, or only for $itemid otherwise).
*
- * @todo this might actually become abstract method
* @param int $raterid
* @param int $itemid
- * @return stdClass newly created or existing record from {grading_instances}
+ * @param boolean $idonly
+ * @return mixed if $idonly=true returns id of the found instance, otherwise returns the instance object
*/
- public function prepare_instance($raterid, $itemid) {
+ public function get_current_instance($raterid, $itemid, $idonly = false) {
global $DB;
-
- if (empty($this->definition)) {
- throw new coding_exception('Attempting to prepare an instance of non-existing grading form');
+ $select = array(
+ 'formid' => $this->definition->id,
+ 'itemid' => $itemid,
+ 'status' => gradingform_instance::INSTANCE_STATUS_ACTIVE);
+ if (false /* TODO $manager->allow_multiple_raters() */) {
+ $select['raterid'] = $raterid;
}
-
- $current = $DB->get_record('grading_instances', array(
- 'formid' => $this->definition->id,
- 'raterid' => $raterid,
- 'itemid' => $itemid), '*', IGNORE_MISSING);
-
- if (empty($current)) {
- $instance = new stdClass();
- $instance->formid = $this->definition->id;
- $instance->raterid = $raterid;
- $instance->itemid = $itemid;
- $instance->timemodified = time();
- $instance->feedbackformat = FORMAT_MOODLE;
- $instance->id = $DB->insert_record('grading_instances', $instance);
- return $instance;
-
+ if ($idonly) {
+ if ($current = $DB->get_record('grading_instances', $select, 'id', IGNORE_MISSING)) {
+ return $current->id;
+ }
} else {
- return $current;
+ if ($current = $DB->get_record('grading_instances', $select, '*', IGNORE_MISSING)) {
+ return $this->get_instance($current);
+ }
}
+ return null;
}
/**
- * Saves non-js data and returns the gradebook grade
- */
- abstract public function save_and_get_grade($raterid, $itemid, $formdata);
-
- /**
- * Returns html for form element
+ * Returns list of active instances for the specified $itemid
+ *
+ * @param int $itemid
+ * @return array of gradingform_instance objects
*/
- abstract public function to_html($gradingformelement);
+ public function get_current_instances($itemid) {
+ global $DB;
+ $conditions = array('formid' => $this->definition->id,
+ 'itemid' => $itemid,
+ 'status' => gradingform_instance::INSTANCE_STATUS_ACTIVE);
+ $records = $DB->get_recordset('grading_instances', $conditions);
+ $rv = array();
+ foreach ($records as $record) {
+ $rv[] = $this->get_instance($record);
+ }
+ return $rv;
+ }
/**
+ * Returns the object of type gradingform_XXX_instance (where XXX is the plugin method name)
*
+ * @param mixed $instance id or row from grading_isntances table
+ * @return gradingform_instance
*/
- public function default_validation_error_message() {
- return '';
+ protected function get_instance($instance) {
+ global $DB;
+ if (is_scalar($instance)) {
+ // instance id is passed as parameter
+ $instance = $DB->get_record('grading_instances', array('id' => $instance), '*', MUST_EXIST);
+ }
+ if ($instance) {
+ $class = 'gradingform_'. $this->get_method_name(). '_instance';
+ return new $class($this, $instance);
+ }
+ return null;
}
/**
+ * This function is invoked when user (teacher) starts grading.
+ * It creates and returns copy of the current ACTIVE instance if it exists. If this is the
+ * first grading attempt, a new instance is created.
+ * The status of the returned instance is INCOMPLETE
*
+ * @param int $raterid
+ * @param int $itemid
+ * @return gradingform_instance
*/
- public function validate_grading_element($elementvalue, $itemid) {
- return true;
+ public function create_instance($raterid, $itemid = null) {
+ global $DB;
+ // first find if there is already an active instance for this itemid
+ if ($itemid && $current = $this->get_current_instance($raterid, $itemid)) {
+ return $this->get_instance($current->copy($raterid, $itemid));
+ } else {
+ $class = 'gradingform_'. $this->get_method_name(). '_instance';
+ return $this->get_instance($class::create_new($this->definition->id, $raterid, $itemid));
+ }
}
/**
@@ -426,4 +457,270 @@ protected function get_method_name() {
throw new coding_exception('Invalid class name');
}
}
+
+ /**
+ * Returns html code to be included in student's feedback.
+ *
+ * @param moodle_page $page
+ * @param int $itemid
+ * @param array $grading_info result of function grade_get_grades if plugin want to use some of their info
+ * @param string $defaultcontent default string to be returned if no active grading is found or for some reason can not be shown to a user
+ * @return string
+ */
+ public function render_grade($page, $itemid, $grading_info, $defaultcontent) {
+ return $defaultcontent;
+ }
+
+ /**
+ * Sets the range of grades used in this area. This is usually either range like 0-100
+ * or the scale where keys start from 1. Typical use:
+ * $controller->set_grade_range(make_grades_menu($gradingtype));
+ */
+ public final function set_grade_range(array $graderange) {
+ $this->graderange = $graderange;
+ }
+
+ /**
+ * Returns the range of grades used in this area
+ * @return array
+ */
+ public final function get_grade_range() {
+ if (empty($this->graderange)) {
+ return array();
+ }
+ return $this->graderange;
+ }
}
+
+/**
+ * Class to manage one grading instance. Stores information and performs actions like
+ * update, copy, validate, submit, etc.
+ *
+ * @copyright 2011 Marina Glancy
+ */
+abstract class gradingform_instance {
+ const INSTANCE_STATUS_ACTIVE = 1;
+ const INSTANCE_STATUS_INCOMPLETE = 0;
+ const INSTANCE_STATUS_ARCHIVE = 3;
+
+ /** @var stdClass record from table grading_instances */
+ protected $data;
+ /** @var gradingform_controller link to the corresponding controller */
+ protected $controller;
+
+ /**
+ * Creates an instance
+ *
+ * @param gradingform_controller $controller
+ * @param stdClass $data
+ */
+ public function __construct($controller, $data) {
+ $this->data = (object)$data;
+ $this->controller = $controller;
+ }
+
+ /**
+ * Creates a new empty instance in DB and mark its status as INCOMPLETE
+ *
+ * @param int $formid
+ * @param int $raterid
+ * @param int $itemid
+ * @return int id of the created instance
+ */
+ public static function create_new($formid, $raterid, $itemid) {
+ global $DB;
+ $instance = new stdClass();
+ $instance->formid = $formid;
+ $instance->raterid = $raterid;
+ $instance->itemid = $itemid;
+ $instance->status = self::INSTANCE_STATUS_INCOMPLETE;
+ $instance->timemodified = time();
+ $instance->feedbackformat = FORMAT_MOODLE;
+ $instanceid = $DB->insert_record('grading_instances', $instance);
+ return $instanceid;
+ }
+
+ /**
+ * Duplicates the instance before editing (optionally substitutes raterid and/or itemid with
+ * the specified values)
+ * Plugins may want to override this function to copy data from additional tables as well
+ *
+ * @param int $raterid value for raterid in the duplicate
+ * @param int $itemid value for itemid in the duplicate
+ * @return int id of the new instance
+ */
+ public function copy($raterid, $itemid) {
+ global $DB;
+ $data = (array)$this->data; // Cast to array to make a copy
+ unset($data['id']);
+ $data['raterid'] = $raterid;
+ $data['itemid'] = $itemid;
+ $data['timemodified'] = time();
+ $data['status'] = self::INSTANCE_STATUS_INCOMPLETE;
+ $instanceid = $DB->insert_record('grading_instances', $data);
+ return $instanceid;
+ }
+
+ /**
+ * Returns the controller
+ *
+ * @return gradingform_controller
+ */
+ public function get_controller() {
+ return $this->controller;
+ }
+
+ /**
+ * Returns instance id
+ *
+ * @return int
+ */
+ public function get_id() {
+ return $this->data->id;
+ }
+
+ /**
+ * Marks the instance as ACTIVE and current active instance (if exists) as ARCHIVE
+ */
+ protected function make_active() {
+ global $DB;
+ if ($this->data->status == self::INSTANCE_STATUS_ACTIVE) {
+ // already active
+ return;
+ }
+ if (empty($this->data->itemid)) {
+ throw new coding_exception('You cannot mark active the grading instance without itemid');
+ }
+ $currentid = $this->get_controller()->get_current_instance($this->data->raterid, $this->data->itemid, true);
+ if ($currentid) {
+ if ($currentid != $this->get_id()) {
+ $DB->update_record('grading_instances', array('id' => $currentid, 'status' => self::INSTANCE_STATUS_ARCHIVE));
+ $DB->update_record('grading_instances', array('id' => $this->get_id(), 'status' => self::INSTANCE_STATUS_ACTIVE));
+ }
+ } else {
+ $DB->update_record('grading_instances', array('id' => $this->get_id(), 'status' => self::INSTANCE_STATUS_ACTIVE));
+ }
+ $this->data->status = self::INSTANCE_STATUS_ACTIVE;
+ }
+
+ /**
+ * Deletes this (INCOMPLETE) instance from database. This function is invoked on cancelling the
+ * grading form and/or during cron cleanup.
+ * Plugins using additional tables must override this method to remove additional data.
+ * Note that if the teacher just closes the window or presses 'Back' button of the browser,
+ * this function is not invoked.
+ */
+ public function cancel() {
+ global $DB;
+ // TODO what if we happen delete the ACTIVE instance, shall we rollback to the last ARCHIVE? or throw an exception?
+ // TODO create cleanup cron
+ $DB->delete_records('grading_instances', array('id' => $this->get_id()));
+ }
+
+ /**
+ * Updates the instance with the data received from grading form. This function may be
+ * called via AJAX when grading is not yet completed, so it does not change the
+ * status of the instance.
+ *
+ * @param array $elementvalue
+ */
+ public function update($elementvalue) {
+ global $DB;
+ $newdata = new stdClass();
+ $newdata->id = $this->get_id();
+ $newdata->timemodified = time();
+ if (isset($elementvalue['itemid']) && $elementvalue['itemid'] != $this->data->itemid) {
+ $newdata->itemid = $elementvalue['itemid'];
+ }
+ // TODO also update: rawgrade, feedback, feedbackformat
+ $DB->update_record('grading_instances', $newdata);
+ foreach ($newdata as $key => $value) {
+ $this->data->$key = $value;
+ }
+ }
+
+ /**
+ * Calculates the grade to be pushed to the gradebook
+ *
+ * @return int the valid grade from $this->get_controller()->get_grade_range()
+ */
+ abstract public function get_grade();
+
+ /**
+ * Called when teacher submits the grading form:
+ * updates the instance in DB, marks it as ACTIVE and returns the grade to be pushed to the gradebook.
+ * $itemid must be specified here (it was not required when the instance was
+ * created, because it might not existed in draft)
+ *
+ * @param array $elementvalue
+ * @param int $itemid
+ * @return int the grade on 0-100 scale
+ */
+ public function submit_and_get_grade($elementvalue, $itemid) {
+ $elementvalue['itemid'] = $itemid;
+ $this->update($elementvalue);
+ $this->make_active();
+ return $this->get_grade();
+ }
+
+
+ /**
+ * Returns html for form element of type 'grading'. If there is a form input element
+ * it must have the name $gradingformelement->getName().
+ * If there are more than one input elements they MUST be elements of array with
+ * name $gradingformelement->getName().
+ * Example: {NAME}[myelement1], {NAME}[myelement2][sub1], {NAME}[myelement2][sub2], etc.
+ * ( {NAME} is a shortcut for $gradingformelement->getName() )
+ * After submitting the form the value of $_POST[{NAME}] is passed to the functions
+ * validate_grading_element() and submit_and_get_grade()
+ *
+ * Plugins may use $gradingformelement->getValue() to get the value passed on previous
+ * form submit
+ *
+ * When forming html it is a plugin's responsibility to analyze flags
+ * $gradingformelement->_flagFrozen and $gradingformelement->_persistantFreeze:
+ *
+ * (_flagFrozen == false) => form element is editable
+ *
+ * (_flagFrozen == false && _persistantFreeze == true) => form element is not editable
+ * but all values are passed as hidden elements
+ *
+ * (_flagFrozen == false && _persistantFreeze == false) => form element is not editable
+ * and no values are passed as hidden elements
+ *
+ * Plugins are welcome to use AJAX in the form element. But it is strongly recommended
+ * that the grading only becomes active when teacher presses 'Submit' button (the
+ * method submit_and_get_grade() is invoked)
+ *
+ * Also client-side JS validation may be implemented here
+ *
+ * @see MoodleQuickForm_grading in lib/form/grading.php
+ *
+ * @param moodle_page $page
+ * @param MoodleQuickForm_grading $gradingformelement
+ * @return string
+ */
+ abstract function render_grading_element($page, $gradingformelement);
+
+ /**
+ * Server-side validation of the data received from grading form.
+ *
+ * @param mixed $elementvalue is the scalar or array received in $_POST
+ * @return boolean true if the form data is validated and contains no errors
+ */
+ public function validate_grading_element($elementvalue) {
+ return true;
+ }
+
+ /**
+ * Returns the error message displayed if validation failed.
+ * If plugin wants to display custom message, the empty string should be returned here
+ * and the custom message should be output in render_grading_element()
+ *
+ * @see validate_grading_element()
+ * @return string
+ */
+ public function default_validation_error_message() {
+ return '';
+ }
+}
View
16 grade/grading/form/rubric/edit.php
@@ -46,16 +46,18 @@
$PAGE->requires->js('/grade/grading/form/rubric/js/rubriceditor.js');
//TODO freeze rubric editor if needed
-$mform = new gradingform_rubric_editrubric(null, array('areaid' => $areaid, 'freezerubric' => optional_param('freeze', 0, PARAM_INT)));
-$mform->set_data($controller->get_definition_for_editing());
+$mform = new gradingform_rubric_editrubric(null, array('areaid' => $areaid, 'context' => $context, 'freezerubric' => optional_param('freeze', 0, PARAM_INT)));
+$data = $controller->get_definition_for_editing();
+$returnurl = optional_param('returnurl', $manager->get_management_url(), PARAM_LOCALURL);
+$data->returnurl = $returnurl;
+$mform->set_data($data);
if ($mform->is_cancelled()) {
// todo process editing cancel in a better way
- redirect($manager->get_management_url());
-
-} else if ($data = $mform->get_data()) {
- $data = $controller->postupdate_definition_data($data);
+ redirect($returnurl);
+} else if ($mform->is_submitted() && $mform->is_validated()) {
+ $data = $mform->get_data();
$controller->update_definition($data);
- redirect($PAGE->url);
+ redirect($returnurl);
}
echo $OUTPUT->header();
View
4 grade/grading/form/rubric/edit_form.php
@@ -44,13 +44,15 @@ public function definition() {
$form->addElement('hidden', 'areaid');
$form->setType('areaid', PARAM_INT);
+ $form->addElement('hidden', 'returnurl');
+
// name
$form->addElement('text', 'name', get_string('name', 'gradingform_rubric'), array('size'=>52));
$form->addRule('name', get_string('required'), 'required');
$form->setType('name', PARAM_TEXT);
// description
- $options = array();
+ $options = gradingform_rubric_controller::description_form_field_options($this->_customdata['context']);
$form->addElement('editor', 'description_editor', get_string('description', 'gradingform_rubric'), null, $options);
$form->setType('description_editor', PARAM_RAW);
View
1  grade/grading/form/rubric/js/rubriceditor.js
@@ -83,6 +83,7 @@ M.gradingform_rubriceditor.editmode = function(el, editmode) {
}
ta.get('parentNode').one('.plainvalue').setStyle('display', 'none')
ta.setStyle('display', 'block').setStyle('width', width).setStyle('height', height)
+ ta.focus()
}
}
View
443 grade/grading/form/rubric/lib.php
@@ -60,19 +60,28 @@ public function extend_settings_navigation(settings_navigation $settingsnav, nav
* Saves the rubric definition into the database
*
* @see parent::update_definition()
- * @param stdClass $newdefinition rubric definition data as coming from {@link self::postupdate_definition_data()}
+ * @param stdClass $newdefinition rubric definition data as coming from gradingform_rubric_editrubric::get_data()
* @param int|null $usermodified optional userid of the author of the definition, defaults to the current user
*/
public function update_definition(stdClass $newdefinition, $usermodified = null) {
global $DB;
// firstly update the common definition data in the {grading_definition} table
+ if ($this->definition === false) {
+ // if definition does not exist yet, create a blank one with only required fields set
+ // (we need id to save files embedded in description)
+ parent::update_definition((object)array('descriptionformat' => FORMAT_MOODLE), $usermodified);
+ parent::load_definition();
+ }
+ $options = self::description_form_field_options($this->get_context());
+ $newdefinition = file_postupdate_standard_editor($newdefinition, 'description', $options, $this->get_context(),
+ 'gradingform_rubric', 'definition_description', $this->definition->id);
parent::update_definition($newdefinition, $usermodified);
+
// reload the definition from the database
- $this->load_definition();
- $currentdefinition = $this->get_definition();
+ $currentdefinition = $this->get_definition(true);
- // update current data
+ // update rubric data
$haschanges = false;
if (empty($newdefinition->rubric_criteria)) {
$newcriteria = array();
@@ -181,7 +190,7 @@ protected function load_definition() {
$this->definition = false;
foreach ($rs as $record) {
// pick the common definition data
- if (empty($this->definition)) {
+ if ($this->definition === false) {
$this->definition = new stdClass();
foreach (array('id', 'name', 'description', 'descriptionformat', 'status', 'copiedfromid',
'timecreated', 'usercreated', 'timemodified', 'usermodified', 'options') as $fieldname) {
@@ -266,182 +275,6 @@ public function get_definition_copy(gradingform_controller $target) {
return $new;
}
- public function get_grading($raterid, $itemid) {
- global $DB;
- $sql = "SELECT f.id, f.criterionid, f.levelid, f.remark, f.remarkformat
- FROM {grading_instances} i, {gradingform_rubric_fillings} f
- WHERE i.formid = :formid ".
- "AND i.raterid = :raterid ".
- "AND i.itemid = :itemid
- AND i.id = f.forminstanceid";
- $params = array('formid' => $this->definition->id, 'itemid' => $itemid, 'raterid' => $raterid);
- $rs = $DB->get_recordset_sql($sql, $params);
- $grading = array();
- foreach ($rs as $record) {
- if ($record->levelid) {
- $grading[$record->criterionid] = $record->levelid;
- }
- // TODO: remarks
- }
- $rs->close();
- return $grading;
- }
-
- /**
- * Converts the rubric data to the gradebook score 0-100
- */
- protected function calculate_grade($grade, $itemid) {
- if (!$this->validate_grading_element($grade, $itemid)) {
- return -1;
- }
-
- $minscore = 0;
- $maxscore = 0;
- foreach ($this->definition->rubric_criteria as $id => $criterion) {
- $keys = array_keys($criterion['levels']);
- // TODO array_reverse($keys) if levels are sorted DESC
- $minscore += $criterion['levels'][$keys[0]]['score'];
- $maxscore += $criterion['levels'][$keys[sizeof($keys)-1]]['score'];
- }
-
- if ($maxscore == 0) {
- return -1;
- }
-
- $curscore = 0;
- foreach ($grade as $id => $levelid) {
- $curscore += $this->definition->rubric_criteria[$id]['levels'][$levelid]['score'];
- }
- return $curscore/$maxscore*100; // TODO mapping
- }
-
- /**
- * Saves non-js data and returns the gradebook grade
- */
- public function save_and_get_grade($raterid, $itemid, $formdata) {
- global $DB, $USER;
- $instance = $this->prepare_instance($raterid, $itemid);
- $currentgrade = $this->get_grading($raterid, $itemid);
- if (!is_array($formdata)) {
- return $this->calculate_grade($currentgrade, $itemid);
- }
- foreach ($formdata as $criterionid => $levelid) {
- $params = array('forminstanceid' => $instance->id, 'criterionid' => $criterionid);
- if (!array_key_exists($criterionid, $currentgrade)) {
- $DB->insert_record('gradingform_rubric_fillings', $params + array('levelid' => $levelid));
- } else if ($currentgrade[$criterionid] != $levelid) {
- $DB->set_field('gradingform_rubric_fillings', 'levelid', $levelid, $params);
- }
- }
- foreach ($currentgrade as $criterionid => $levelid) {
- if (!array_key_exists($criterionid, $formdata)) {
- $params = array('forminstanceid' => $instance->id, 'criterionid' => $criterionid);
- $DB->delete_records('gradingform_rubric_fillings', $params);
- }
- }
- // TODO: remarks
- return $this->calculate_grade($formdata, $itemid);
- }
-
- /**
- * Returns html for form element
- */
- public function to_html($gradingformelement) {
- global $PAGE, $USER;
- if (!$gradingformelement->_flagFrozen) {
- $module = array('name'=>'gradingform_rubric', 'fullpath'=>'/grade/grading/form/rubric/js/rubric.js');
- $PAGE->requires->js_init_call('M.gradingform_rubric.init', array(array('name' => $gradingformelement->getName(), 'criteriontemplate' =>'', 'leveltemplate' => '')), true, $module);
- $mode = self::DISPLAY_EVAL;
- } else {
- if ($this->_persistantFreeze) {
- $mode = gradingform_rubric_controller::DISPLAY_EVAL_FROZEN;
- } else {
- $mode = gradingform_rubric_controller::DISPLAY_REVIEW;
- }
- }
- $criteria = $this->definition->rubric_criteria;
- $submissionid = $gradingformelement->get_grading_attribute('submissionid');
- $raterid = $USER->id; // TODO - this is very strange!
- $value = $gradingformelement->getValue();
- if ($value === null) {
- $value = $this->get_grading($raterid, $submissionid); // TODO maybe implement in form->set_data() ?
- }
- return $this->get_renderer($PAGE)->display_rubric($criteria, $mode, $gradingformelement->getName(), $value);
- }
-
- /**
- * Returns html for form element
- */
- public function to_html_old($gradingformelement) {
- global $PAGE, $USER;
- //TODO move to renderer
-
- //$gradingrenderer = $this->prepare_renderer($PAGE);
- $html = '';
- $elementname = $gradingformelement->getName();
- $elementvalue = $gradingformelement->getValue();
- $submissionid = $gradingformelement->get_grading_attribute('submissionid');
- $raterid = $USER->id; // TODO - this is very strange!
- $html .= "assessing submission $submissionid<br />";
- //$html .= html_writer::empty_tag('input', array('type' => 'text', 'name' => $elementname.'[grade]', 'size' => '20', 'value' => $elementvalue['grade']));
-
- if (!$gradingformelement->_flagFrozen) {
- $module = array('name'=>'gradingform_rubric', 'fullpath'=>'/grade/grading/form/rubric/js/rubric.js');
- $PAGE->requires->js_init_call('M.gradingform_rubric.init', array(array('name' => $gradingformelement->getName(), 'criteriontemplate' =>'', 'leveltemplate' => '')), true, $module);
- }
- $criteria = $this->definition->rubric_criteria;
-
- $html .= html_writer::start_tag('div', array('id' => 'rubric-'.$gradingformelement->getName(), 'class' => 'form_rubric evaluate'));
- $criteria_cnt = 0;
-
- $value = $gradingformelement->getValue();
- if ($value === null) {
- $value = $this->get_grading($raterid, $submissionid); // TODO maybe implement in form->set_data() ?
- }
-
- foreach ($criteria as $criterionid => $criterion) {
- $html .= html_writer::start_tag('div', array('class' => 'criterion'.$this->get_css_class_suffix($criteria_cnt++, count($criteria)-1)));
- $html .= html_writer::tag('div', $criterion['description'], array('class' => 'description')); // TODO descriptionformat
- $html .= html_writer::start_tag('div', array('class' => 'levels'));
- $level_cnt = 0;
- foreach ($criterion['levels'] as $levelid => $level) {
- $checked = (is_array($value) && array_key_exists($criterionid, $value) && ((int)$value[$criterionid] === $levelid));
- $classsuffix = $this->get_css_class_suffix($level_cnt++, count($criterion['levels'])-1);
- if ($checked) {
- $classsuffix .= ' checked';
- }
- $html .= html_writer::start_tag('div', array('id' => $gradingformelement->getName().'-'.$criterionid.'-levels-'.$levelid, 'class' => 'level'.$classsuffix));
- $input = html_writer::empty_tag('input', array('type' => 'radio', 'name' => $gradingformelement->getName().'['.$criterionid.']', 'value' => $levelid) +
- ($checked ? array('checked' => 'checked') : array())); // TODO rewrite
- $html .= html_writer::tag('div', $input, array('class' => 'radio'));
- $html .= html_writer::tag('div', $level['definition'], array('class' => 'definition')); // TODO definitionformat
- $html .= html_writer::tag('div', (float)$level['score'].' pts', array('class' => 'score')); //TODO span, get_string
- $html .= html_writer::end_tag('div'); // .level
- }
- $html .= html_writer::end_tag('div'); // .levels
- $html .= html_writer::end_tag('div'); // .criterion
- }
- $html .= html_writer::end_tag('div'); // .rubric
- return $html;
-
- }
-
- private function get_css_class_suffix($cnt, $maxcnt) {
- $class = '';
- if ($cnt == 0) {
- $class .= ' first';
- }
- if ($cnt == $maxcnt) {
- $class .= ' last';
- }
- if ($cnt%2) {
- $class .= ' odd';
- } else {
- $class .= ' even';
- }
- return $class;
- }
-
// TODO the following functions may be moved to parent:
/**
@@ -457,7 +290,7 @@ public static function description_form_field_options($context) {
}
public function get_formatted_description() {
- if (!$this->definition) {
+ if ($this->definition === false) {
return null;
}
$context = $this->get_context();
@@ -475,53 +308,12 @@ public function get_formatted_description() {
return format_text($description, $this->definition->descriptionformat, $formatoptions);
}
- /**
- * Converts the rubric definition data from the submitted form back to the form
- * suitable for storing in database
- */
- public function postupdate_definition_data($data) {
- if (!$this->definition) {
- return $data;
- }
- $options = self::description_form_field_options($this->get_context());
- $data = file_postupdate_standard_editor($data, 'description', $options, $this->get_context(),
- 'gradingform_rubric', 'definition_description', $this->definition->id);
- // TODO change filearea for embedded files in grading_definition.description
- return $data;
- }
-
public function is_form_available($foruserid = null) {
return true;
// TODO this is temporary for testing!
}
/**
- * Returns the error message displayed in case of validation failed
- *
- * @see validate_grading_element
- */
- public function default_validation_error_message() {
- return 'The rubric is incomplete'; //TODO string
- }
-
- /**
- * Validates that rubric is fully completed and contains valid grade on each criterion
- */
- public function validate_grading_element($elementvalue, $itemid) {
- // TODO: if there is nothing selected in rubric, we don't enter this function at all :(
- $criteria = $this->definition->rubric_criteria;
- if (!is_array($elementvalue) || sizeof($elementvalue) < sizeof($criteria)) {
- return false;
- }
- foreach ($criteria as $id => $criterion) {
- if (!array_key_exists($id, $elementvalue) || !array_key_exists($elementvalue[$id], $criterion['levels'])) {
- return false;
- }
- }
- return true;
- }
-
- /**
* Returns the rubric plugin renderer
*
* @param moodle_page $page the target page
@@ -544,8 +336,8 @@ public function render_preview(moodle_page $page) {
// append the rubric itself, using own renderer
$output = $this->get_renderer($page);
- // todo something like $rubric = $output->render_preview($this);
- $rubric = '[[TODO RUBRIC PREVIEW]]';
+ $criteria = $this->definition->rubric_criteria;
+ $rubric = $output->display_rubric($criteria, self::DISPLAY_PREVIEW, 'rubric');
return $header . $rubric;
}
@@ -569,4 +361,205 @@ protected function delete_plugin_definition() {
// delete critera
$DB->delete_records_list('gradingform_rubric_criteria', 'id', $criteria);
}
+
+ /**
+ * Returns html code to be included in student's feedback.
+ *
+ * @param moodle_page $page
+ * @param int $itemid
+ * @param array $grading_info result of function grade_get_grades
+ * @param string $defaultcontent default string to be returned if no active grading is found
+ * @return string
+ */
+ public function render_grade($page, $itemid, $grading_info, $defaultcontent) {
+ $instances = $this->get_current_instances($itemid);
+ return $this->get_renderer($page)->display_instances($this->get_current_instances($itemid), $defaultcontent);
+ }
}
+
+/**
+ * Class to manage one rubric grading instance. Stores information and performs actions like
+ * update, copy, validate, submit, etc.
+ *
+ * @copyright 2011 Marina Glancy
+ */
+class gradingform_rubric_instance extends gradingform_instance {
+
+ protected $rubric;
+
+ /**
+ * Deletes this (INCOMPLETE) instance from database.
+ */
+ public function cancel() {
+ global $DB;
+ parent::cancel();
+ $DB->delete_records('gradingform_rubric_fillings', array('forminstanceid' => $this->get_id()));
+ }
+
+ /**
+ * Duplicates the instance before editing (optionally substitutes raterid and/or itemid with
+ * the specified values)
+ *
+ * @param int $raterid value for raterid in the duplicate
+ * @param int $itemid value for itemid in the duplicate
+ * @return int id of the new instance
+ */
+ public function copy($raterid, $itemid) {
+ global $DB;
+ $instanceid = parent::copy($raterid, $itemid);
+ $currentgrade = $this->get_rubric_filling();
+ foreach ($currentgrade['criteria'] as $criterionid => $record) {
+ $params = array('forminstanceid' => $instanceid, 'criterionid' => $criterionid,
+ 'levelid' => $record['levelid'], 'remark' => $record['remark'], 'remarkformat' => $record['remarkformat']);
+ $DB->insert_record('gradingform_rubric_fillings', $params);
+ }
+ return $instanceid;
+ }
+
+ /**
+ * Validates that rubric is fully completed and contains valid grade on each criterion
+ * @return boolean true if the form data is validated and contains no errors
+ */
+ public function validate_grading_element($elementvalue) {
+ // TODO: if there is nothing selected in rubric, we don't enter this function at all :(
+ $criteria = $this->get_controller()->get_definition()->rubric_criteria;
+ if (!isset($elementvalue['criteria']) || !is_array($elementvalue['criteria']) || sizeof($elementvalue['criteria']) < sizeof($criteria)) {
+ return false;
+ }
+ foreach ($criteria as $id => $criterion) {
+ if (!isset($elementvalue['criteria'][$id]['levelid'])
+ || !array_key_exists($elementvalue['criteria'][$id]['levelid'], $criterion['levels'])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Retrieves from DB and returns the data how this rubric was filled
+ *
+ * @param boolean $force whether to force DB query even if the data is cached
+ * @return array
+ */
+ public function get_rubric_filling($force = false) {
+ global $DB;
+ if ($this->rubric === null || $force) {
+ $records = $DB->get_records('gradingform_rubric_fillings', array('forminstanceid' => $this->get_id()));
+ $this->rubric = array('criteria' => array());
+ foreach ($records as $record) {
+ $this->rubric['criteria'][$record->criterionid] = (array)$record;
+ }
+ }
+ return $this->rubric;
+ }
+
+ /**
+ * Updates the instance with the data received from grading form. This function may be
+ * called via AJAX when grading is not yet completed, so it does not change the
+ * status of the instance.
+ *
+ * @param array $data
+ */
+ public function update($data) {
+ global $DB;
+ $currentgrade = $this->get_rubric_filling();
+ parent::update($data);
+ foreach ($data['criteria'] as $criterionid => $record) {
+ if (!array_key_exists($criterionid, $currentgrade['criteria'])) {
+ $newrecord = array('forminstanceid' => $this->get_id(), 'criterionid' => $criterionid,
+ 'levelid' => $record['levelid'], 'remark' => $record['remark'], 'remarkformat' => FORMAT_MOODLE);
+ $DB->insert_record('gradingform_rubric_fillings', $newrecord);
+ } else {
+ $newrecord = array('id' => $currentgrade['criteria'][$criterionid]['id']);
+ foreach (array('levelid', 'remark'/*, 'remarkformat' TODO */) as $key) {
+ if ($currentgrade['criteria'][$criterionid][$key] != $record[$key]) {
+ $newrecord[$key] = $record[$key];
+ }
+ }
+ if (count($newrecord) > 1) {
+ $DB->update_record('gradingform_rubric_fillings', $newrecord);
+ }
+ }
+ }
+ foreach ($currentgrade['criteria'] as $criterionid => $record) {
+ if (!array_key_exists($criterionid, $data['criteria'])) {
+ $DB->delete_records('gradingform_rubric_fillings', array('id' => $record['id']));
+ }
+ }
+ $this->get_rubric_filling(true);
+ }
+
+ /**
+ * Calculates the grade to be pushed to the gradebook
+ *
+ * @return int the valid grade from $this->get_controller()->get_grade_range()
+ */
+ public function get_grade() {
+ global $DB, $USER;
+ $grade = $this->get_rubric_filling();
+
+ $minscore = 0;
+ $maxscore = 0;
+ foreach ($this->get_controller()->get_definition()->rubric_criteria as $id => $criterion) {
+ $keys = array_keys($criterion['levels']);
+ sort($keys);
+ $minscore += $criterion['levels'][$keys[0]]['score'];
+ $maxscore += $criterion['levels'][$keys[sizeof($keys)-1]]['score'];
+ }
+
+ if ($maxscore <= $minscore) {
+ return -1;
+ }
+
+ $graderange = array_keys($this->get_controller()->get_grade_range());
+ if (empty($graderange)) {
+ return -1;
+ }
+ sort($graderange);
+ $mingrade = $graderange[0];
+ $maxgrade = $graderange[sizeof($graderange) - 1];
+
+ $curscore = 0;
+ foreach ($grade['criteria'] as $id => $record) {
+ $curscore += $this->get_controller()->get_definition()->rubric_criteria[$id]['levels'][$record['levelid']]['score'];
+ }
+ return round(($curscore-$minscore)/($maxscore-$minscore)*($maxgrade-$mingrade), 0) + $mingrade; // TODO mapping
+ }
+
+ /**
+ * Returns the error message displayed in case of validation failed
+ *
+ * @return string
+ */
+ public function default_validation_error_message() {
+ return 'The rubric is incomplete'; //TODO string
+ }
+
+ /**
+ * Returns html for form element of type 'grading'.
+ *
+ * @param moodle_page $page
+ * @param MoodleQuickForm_grading $formelement
+ * @return string
+ */
+ public function render_grading_element($page, $gradingformelement) {
+ global $USER;
+ if (!$gradingformelement->_flagFrozen) {
+ $module = array('name'=>'gradingform_rubric', 'fullpath'=>'/grade/grading/form/rubric/js/rubric.js');
+ $page->requires->js_init_call('M.gradingform_rubric.init', array(array('name' => $gradingformelement->getName())), true, $module);
+ $mode = gradingform_rubric_controller::DISPLAY_EVAL;
+ } else {
+ if ($gradingformelement->_persistantFreeze) {
+ $mode = gradingform_rubric_controller::DISPLAY_EVAL_FROZEN;
+ } else {
+ $mode = gradingform_rubric_controller::DISPLAY_REVIEW;
+ }
+ }
+ $criteria = $this->get_controller()->get_definition()->rubric_criteria;
+ $value = $gradingformelement->getValue();
+ if ($value === null) {
+ $value = $this->get_rubric_filling();
+ }
+ return $this->get_controller()->get_renderer($page)->display_rubric($criteria, $mode, $gradingformelement->getName(), $value);
+ }
+}
View
65 grade/grading/form/rubric/renderer.php
@@ -35,8 +35,8 @@ class gradingform_rubric_renderer {
* @param int $mode @see gradingform_rubric_controller
* @return string
*/
- public function criterion_template($mode, $elementname = '{NAME}', $criterion = null, $levels_str = '{LEVELS}') {
- // TODO description format
+ public function criterion_template($mode, $elementname = '{NAME}', $criterion = null, $levels_str = '{LEVELS}', $value = null) {
+ // TODO description format, remark format
if ($criterion === null || !is_array($criterion) || !array_key_exists('id', $criterion)) {
$criterion = array('id' => '{CRITERION-id}', 'description' => '{CRITERION-description}', 'sortorder' => '{CRITERION-sortorder}', 'class' => '{CRITERION-class}');
} else {
@@ -74,6 +74,21 @@ public function criterion_template($mode, $elementname = '{NAME}', $criterion =
'id' => '{NAME}-{CRITERION-id}-levels-addlevel', 'value' => $value, 'title' => $value)); //TODO '{NAME}-{CRITERION-id}-levels-addlevel
$criterion_template .= html_writer::tag('div', $button, array('class' => 'addlevel'));
}
+ if (isset($value['remark'])) {
+ $currentremark = $value['remark'];
+ } else {
+ $currentremark = '';
+ }
+ if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
+ $input = html_writer::tag('textarea', htmlspecialchars($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
+ $criterion_template .= html_writer::tag('div', $input, array('class' => 'remark'));
+ }
+ if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
+ $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
+ }
+ if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW) {
+ $criterion_template .= html_writer::tag('div', $currentremark, array('class' => 'remark')); // TODO maybe some prefix here like 'Teacher remark:'
+ }
$criterion_template .= html_writer::end_tag('div'); // .criterion
$criterion_template = str_replace('{NAME}', $elementname, $criterion_template);
@@ -83,7 +98,7 @@ public function criterion_template($mode, $elementname = '{NAME}', $criterion =
public function level_template($mode, $elementname = '{NAME}', $criterionid = '{CRITERION-id}', $level = null) {
// TODO definition format
- if ($level === null || !is_array($level) || !array_key_exists('id', $level)) {
+ if (!isset($level['id'])) {
$level = array('id' => '{LEVEL-id}', 'definition' => '{LEVEL-definition}', 'score' => '{LEVEL-score}', 'class' => '{LEVEL-class}', 'checked' => false);
} else {
foreach (array('score', 'definition', 'class', 'checked') as $key) {
@@ -108,12 +123,12 @@ public function level_template($mode, $elementname = '{NAME}', $criterionid = '{
$score = $level['score'];
}
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
- $input = html_writer::empty_tag('input', array('type' => 'radio', 'name' => '{NAME}[{CRITERION-id}]', 'value' => $level['id']) +
+ $input = html_writer::empty_tag('input', array('type' => 'radio', 'name' => '{NAME}[criteria][{CRITERION-id}][levelid]', 'value' => $level['id']) +
($level['checked'] ? array('checked' => 'checked') : array()));
$level_template .= html_writer::tag('div', $input, array('class' => 'radio'));
}
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN && $level['checked']) {
- $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}]', 'value' => $level['id']));
+ $level_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levelid]', 'value' => $level['id']));
}
$score = html_writer::tag('span', $score, array('id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-score'));
$level_template .= html_writer::tag('div', $definition, array('class' => 'definition', 'id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-definition'));
@@ -176,17 +191,22 @@ public function display_rubric($criteria, $mode, $elementname = null, $values =
$criterion['class'] = $this->get_css_class_suffix($cnt++, sizeof($criteria) -1);
$levels_str = '';
$levelcnt = 0;
+ if (isset($values['criteria'][$id])) {
+ $criterionvalue = $values['criteria'][$id];
+ } else {
+ $criterionvalue = null;
+ }
foreach ($criterion['levels'] as $levelid => $level) {
$level['score'] = (float)$level['score']; // otherwise the display will look like 1.00000
$level['class'] = $this->get_css_class_suffix($levelcnt++, sizeof($criterion['levels']) -1);
- $level['checked'] = (is_array($values) && (array_key_exists($id, $values) && ((int)$values[$id] === $levelid)));
+ $level['checked'] = (isset($criterionvalue['levelid']) && ((int)$criterionvalue['levelid'] === $levelid));
if ($level['checked'] && ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN || $mode == gradingform_rubric_controller::DISPLAY_REVIEW)) {
$level['class'] .= ' checked';
//in mode DISPLAY_EVAL the class 'checked' will be added by JS if it is enabled. If it is not enabled, the 'checked' class will only confuse
}
$levels_str .= $this->level_template($mode, $elementname, $id, $level);
}
- $criteria_str .= $this->criterion_template($mode, $elementname, $criterion, $levels_str);
+ $criteria_str .= $this->criterion_template($mode, $elementname, $criterion, $levels_str, $criterionvalue);
}
return $this->rubric_template($mode, $elementname, $criteria_str);
}
@@ -213,4 +233,35 @@ private function get_css_class_suffix($cnt, $maxcnt) {
}
return $class;
}
+
+ /**
+ * Displays for the student the list of instances or default content if no instances found
+ *
+ * @param array $instances array of objects of type gradingform_rubric_instance
+ * @param string $defaultcontent default string that would be displayed without advanced grading
+ * @return string
+ */
+ public function display_instances($instances, $defaultcontent) {
+ if (sizeof($instances)) {
+ $rv = html_writer::start_tag('div', array('class' => 'advancedgrade'));
+ $idx = 0;
+ foreach ($instances as $instance) {
+ $rv .= $this->display_instance($instance, $idx++);
+ }
+ $rv .= html_writer::end_tag('div');
+ }
+ return $rv. $defaultcontent;
+ }
+
+ /**
+ * Displays one grading instance
+ *
+ * @param gradingform_rubric_instance $instance
+ * @param int idx unique number of instance on page
+ */
+ public function display_instance(gradingform_rubric_instance $instance, $idx) {
+ $criteria = $instance->get_controller()->get_definition()->rubric_criteria;
+ $values = $instance->get_rubric_filling();
+ return $this->display_rubric($criteria, gradingform_rubric_controller::DISPLAY_REVIEW, 'rubric'.$idx, $values);
+ }
}
View
6 grade/grading/form/rubric/styles.css
@@ -20,6 +20,8 @@
[input type=submit]
.addlevel
[input type=submit]
+ .remark
+ textarea
.addcriterion
[input type=submit]
@@ -41,11 +43,13 @@
.form_rubric .criterion .description,
.form_rubric .criterion .levels,
.form_rubric.editor .criterion .addlevel,
+.form_rubric .criterion .remark,
.form_rubric .criterion .levels .level {display: inline-block; vertical-align: top;overflow: hidden;}
.form_rubric.editor .criterion .controls,
.form_rubric .criterion .description,
.form_rubric.editor .criterion .addlevel,
+.form_rubric .criterion .remark,
.form_rubric .criterion .levels .level {padding:3px;}
/* Those divs should extend vertically and fill 100% of parent element height */
@@ -71,7 +75,7 @@
.form_rubric.editor .criterion.last .controls .movedown input {display:none;}
/* evaluation */
-.form_rubric.evaluate .criterion .levels .level.checked {background:#d0ffd0;}
+.form_rubric .criterion .levels .level.checked {background:#d0ffd0;}
.form_rubric.evaluate .criterion .levels .level:hover {background:#30ff30;}
/* replace buttons with images */
View
46 lib/form/grading.php
@@ -31,7 +31,11 @@
}
/**
- * HTML class for a grading element
+ * HTML class for a grading element. This is a wrapper for advanced grading plugins.
+ * When adding the 'grading' element to the form, developer must pass an object of
+ * class gradingform_instance as $attributes['gradinginstance']. Otherwise an exception will be
+ * thrown.
+ * This object is responsible for implementing functions to render element html and validate it
*
* @author Marina Glancy
* @access public
@@ -44,6 +48,10 @@ class MoodleQuickForm_grading extends HTML_QuickForm_input{
*/
var $_helpbutton='';
+ /**
+ * Stores attributes passed to the element
+ * @var array
+ */
private $gradingattributes;
function MoodleQuickForm_grading($elementName=null, $elementLabel=null, $attributes=null) {
@@ -51,16 +59,27 @@ function MoodleQuickForm_grading($elementName=null, $elementLabel=null, $attribu
$this->gradingattributes = $attributes;
}
- function toHtml(){
- return $this->get_controller()->to_html($this);
- }
-
- function get_grading_attribute($name) {
- return $this->gradingattributes[$name];
+ /**
+ * Helper function to retrieve gradingform_instance passed in element attributes
+ *
+ * @return gradingform_instance
+ */
+ function get_gradinginstance() {
+ if (is_array($this->gradingattributes) && array_key_exists('gradinginstance', $this->gradingattributes)) {
+ return $this->gradingattributes['gradinginstance'];
+ } else {
+ return null;
+ }
}
- function get_controller() {
- return $this->get_grading_attribute('controller');
+ /**
+ * Returns the input field in HTML
+ *
+ * @return string
+ */
+ function toHtml(){
+ global $PAGE;
+ return $this->get_gradinginstance()->render_grading_element($PAGE, $this);
}
/**
@@ -97,22 +116,23 @@ function getElementTemplateType(){
function onQuickFormEvent($event, $arg, &$caller) {
if ($event == 'createElement') {
$attributes = $arg[2];
- if (!is_array($attributes) || !array_key_exists('controller', $attributes) || !($attributes['controller'] instanceof gradingform_controller)) {
+ if (!is_array($attributes) || !array_key_exists('gradinginstance', $attributes) || !($attributes['gradinginstance'] instanceof gradingform_instance)) {
throw new moodle_exception('exc_gradingformelement', 'grading');
}
}
$name = $this->getName();
if ($name && $caller->elementExists($name)) {
- $caller->addRule($name, $this->get_controller()->default_validation_error_message(), 'gradingvalidated', $this->gradingattributes);
+ $caller->addRule($name, $this->get_gradinginstance()->default_validation_error_message(), 'gradingvalidated', $this->gradingattributes);
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
- * Function registered as rule for this element and is called when this element is being validated
+ * Function registered as rule for this element and is called when this element is being validated.
+ * This is a wrapper to pass the validation to the method gradingform_instance::validate_grading_element
*/
static function _validate($elementValue, $attributes = null) {
- return $attributes['controller']->validate_grading_element($elementValue, $attributes['submissionid']);
+ return $attributes['gradinginstance']->validate_grading_element($elementValue);
}
}
View
150 mod/assignment/lib.php
@@ -264,8 +264,9 @@ function view_footer() {
* @param object $submission The submission object or NULL in which case it will be loaded
*/
function view_feedback($submission=NULL) {
- global $USER, $CFG, $DB, $OUTPUT;
+ global $USER, $CFG, $DB, $OUTPUT, $PAGE;
require_once($CFG->libdir.'/gradelib.php');
+ require_once("$CFG->dirroot/grade/grading/lib.php");
if (!is_enrolled($this->context, $USER, 'mod/assignment:view')) {
// can not submit assignments -> no feedback
@@ -329,9 +330,13 @@ function view_feedback($submission=NULL) {
echo '<tr>';
echo '<td class="left side">&nbsp;</td>';
echo '<td class="content">';
- echo '<div class="grade">';
- echo get_string("grade").': '.$grade->str_long_grade;
- echo '</div>';
+ $grade_str = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
+ if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
+ $controller->set_grade_range(make_grades_menu($this->assignment->grade));
+ echo $controller->render_grade($PAGE, $submission->id, $item, $grade_str);
+ } else {
+ echo $grade_str;
+ }
echo '<div class="clearer"></div>';
echo '<div class="comment">';
@@ -608,7 +613,6 @@ function submissions($mode) {
//make user global so we can use the id
global $USER, $OUTPUT, $DB, $PAGE;
-
$mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
if (optional_param('next', null, PARAM_BOOL)) {
@@ -628,12 +632,15 @@ function submissions($mode) {
set_user_preference('assignment_mailinfo', $mailinfo);
}
+ if (!($this->validate_and_preprocess_feedback())) {
+ // form was submitted ('Save' or 'Save and next' was pressed, but validation failed)
+ $this->display_submission();
+ return;
+ }
+
switch ($mode) {
case 'grade': // We are in a main window grading
- if (!$this->validate_and_preprocess_feedback()) {
- // validation failed
- $this->display_submission();
- } else if ($submission = $this->process_feedback()) {
+ if ($submission = $this->process_feedback()) {
$this->display_submissions(get_string('changessaved'));
} else {
$this->display_submissions();
@@ -747,11 +754,7 @@ function submissions($mode) {
case 'saveandnext':
///We are in pop up. save the current one and go to the next one.
//first we save the current changes
- if (!$this->validate_and_preprocess_feedback()) {
- // validation failed
- $this->display_submission();
- break;
- } else if ($submission = $this->process_feedback()) {
+ if ($submission = $this->process_feedback()) {
//print_heading(get_string('changessaved'));
//$extra_javascript = $this->update_main_listing($submission);
}
@@ -779,6 +782,23 @@ function submissions($mode) {
}
/**
+ * Checks if grading method allows quickgrade mode. At the moment it is hardcoded
+ * that advanced grading methods do not allow quickgrade.
+ *
+ * Assignment type plugins are not allowed to override this method
+ *
+ * @return boolean
+ */
+ public final function quickgrade_mode_allowed() {
+ global $CFG;
+ require_once("$CFG->dirroot/grade/grading/lib.php");
+ if ($controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Helper method updating the listing on the main script from popup using javascript
*
* @global object
@@ -792,7 +812,7 @@ function update_main_listing($submission) {
$perpage = get_user_preferences('assignment_perpage', 10);
- $quickgrade = get_user_preferences('assignment_quickgrade', 0);
+ $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
/// Run some Javascript to try and update the parent page
$output .= '<script type="text/javascript">'."\n<!--\n";
@@ -925,7 +945,7 @@ function display_grade($grade) {
* @param string $extra_javascript
*/
function display_submission($offset=-1,$userid =-1, $display=true) {
- global $CFG, $DB, $PAGE, $OUTPUT;
+ global $CFG, $DB, $PAGE, $OUTPUT, $USER;
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/tablelib.php');
require_once("$CFG->dirroot/repository/lib.php");
@@ -1046,41 +1066,12 @@ function display_submission($offset=-1,$userid =-1, $display=true) {
} elseif ($assignment->assignmenttype == 'uploadsingle') {
$mformdata->fileui_options = array('subdirs'=>0, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
}
- /*$gradingman = get_grading_manager($this->context, 'mod_assignment', 'submission');
- if ($gradingmethod = $gradingman->get_active_method()) {
- $controller = $gradingman->get_controller($gradingmethod);
- if ($controller->is_form_available()) {
- if (empty($submission->id)) {
- $mformdata->advancedgradingenabled = true;
- $mformdata->advancedgradingwidget = get_string('noitemid', 'core_grading');
- } else {
- $gradingrenderer = $controller->prepare_renderer($PAGE);
- if ($this->assignment->grade < 0) {
- $options = array(
- 'displayas' => 'scale',
- 'scaleid' => -$this->assignment->grade);
- } else {
- $options = array(
- 'displayas' => 'grade',
- 'maxgrade' => $this->assignment->grade,
- 'decimals' => 0);
- }
- $gradingwidget = $controller->make_grading_widget($USER->id, $submission->id, $options);
- if ($gradingwidget instanceof renderable) {
- $mformdata->advancedgradingenabled = true;
- $mformdata->advancedgradingwidget = $gradingrenderer->render($gradingwidget);
- }
- }
- } else {
- notice(get_string('formnotavailable', 'core_grading'), new moodle_url('/course/view.php', array('id' => $assignment->course)));
- }
- }*/
if ($controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
- if (!isset($submission->id)) {
- // TODO this is a patch if submission id does not exist yet
- $mformdata->submission = $this->get_submission($user->id, true);
+ $itemid = null;
+ if (!empty($submission->id)) {
+ $itemid = $submission->id;
}
- $mformdata->advancedgradingcontroller = $controller;
+ $mformdata->advancedgradinginstance = $controller->create_instance($USER->id, $itemid);
}
$submitform = new mod_assignment_grading_form( null, $mformdata );
@@ -1165,7 +1156,7 @@ function display_submissions($message='') {
* from database
*/
$perpage = get_user_preferences('assignment_perpage', 10);
- $quickgrade = get_user_preferences('assignment_quickgrade', 0);
+ $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
$filter = get_user_preferences('assignment_filter', 0);
$grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
@@ -1394,6 +1385,7 @@ function display_submissions($message='') {
/// Calculate user status
$auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
+ // TODO add here code if advanced grading grade must be reviewed => $auser->status=0
$picture = $OUTPUT->user_picture($auser);
if (empty($auser->submissionid)) {
@@ -1579,9 +1571,11 @@ function display_submissions($message='') {
$mform->addElement('text', 'perpage', get_string('pagesize', 'assignment'), array('size'=>1));
$mform->setDefault('perpage', $perpage);
- $mform->addElement('checkbox', 'quickgrade', get_string('quickgrade','assignment'));
- $mform->setDefault('quickgrade', $quickgrade);
- $mform->addHelpButton('quickgrade', 'quickgrade', 'assignment');
+ if ($this->quickgrade_mode_allowed()) {
+ $mform->addElement('checkbox', 'quickgrade', get_string('quickgrade','assignment'));
+ $mform->setDefault('quickgrade', $quickgrade);
+ $mform->addHelpButton('quickgrade', 'quickgrade', 'assignment');
+ }
$mform->addElement('submit', 'savepreferences', get_string('savepreferences'));
@@ -1591,28 +1585,37 @@ function display_submissions($message='') {
}
/**
- * Validates the submitted form and returns false if validation did not pass.
+ * If the form was cancelled ('Cancel' or 'Next' was pressed), call cancel method
+ * from advanced grading (if applicable) and returns true
+ * If the form was submitted, validates it and returns false if validation did not pass.
* If validation passes, preprocess advanced grading (if applicable) and returns true.
*/
function validate_and_preprocess_feedback() {
global $USER;
- if (!$feedback = data_submitted()) {
+ if (!($feedback = data_submitted()) || !isset($feedback->userid) || !isset($feedback->offset)) {
return true; // No incoming data, nothing to validate
}
$userid = required_param('userid', PARAM_INT);
$offset = required_param('offset', PARAM_INT);
$submissiondata = $this->display_submission($offset, $userid, false);
$mform = $submissiondata->mform;
- if ($mform->is_submitted()) {
+ $gradinginstance = $mform->use_advanced_grading();
+ if (optional_param('cancel', false, PARAM_BOOL) || optional_param('next', false, PARAM_BOOL)) {
+ // form was cancelled
+ if ($gradinginstance) {
+ $gradinginstance->cancel();
+ }
+ } else if ($mform->is_submitted()) {
+ // form was submitted (= a submit button other than 'cancel' or 'next' has been clicked)
if (!$mform->is_validated()) {
return false;
}
// preprocess advanced grading here
- if ($controller = $mform->use_advanced_grading()) {
+ if ($gradinginstance) {
$data = $mform->get_data();
- // TODO find better way to find submission id
- $submission = $this->get_submission($userid);
- $_POST['xgrade'] = $controller->save_and_get_grade($USER->id /* TODO */, $submission->id, $data->advancedgrading);
+ // create submission if it did not exist yet because we need submission->id for storing the grading instance
+ $submission = $this->get_submission($userid, true);
+ $_POST['xgrade'] = $gradinginstance->submit_and_get_grade($data->advancedgrading, $submission->id);
}
}
return true;
@@ -2300,8 +2303,8 @@ function definition() {
global $OUTPUT;
$mform =& $this->_form;
- if (isset($this->_customdata->advancedgradingcontroller)) {
- $this->use_advanced_grading($this->_customdata->advancedgradingcontroller);
+ if (isset($this->_customdata->advancedgradinginstance)) {
+ $this->use_advanced_grading($this->_customdata->advancedgradinginstance);
}
$formattr = $mform->getAttributes();
@@ -2349,17 +2352,17 @@ function definition() {
}
- private $_advancegradingcontroller;
+ private $advancegradinginstance;
/**
- * Gets or sets the controller for advanced grading
+ * Gets or sets the instance for advanced grading
*
- * @param <type> $controller
+ * @param gradingform_instance $gradinginstance
*/
- public function use_advanced_grading($controller = false) {
- if ($controller !== false) {
- $this->_advancegradingcontroller = $controller;
+ public function use_advanced_grading($gradinginstance = false) {
+ if ($gradinginstance !== false) {
+ $this->advancegradinginstance = $gradinginstance;
}
- return $this->_advancegradingcontroller;
+ return $this->advancegradinginstance;
}
function add_grades_section() {
@@ -2372,13 +2375,12 @@ function add_grades_section() {
$mform->addElement('header', 'Grades', get_string('grades', 'grades'));
- if ($controller = $this->use_advanced_grading()) {
- // TODO what if submission id does not exist yet!
- $mform->addElement('grading', 'advancedgrading', get_string('grade').':',
- array('controller' => $controller, 'submissionid' => $this->_customdata->submission->id));
+ $grademenu = make_grades_menu($this->_customdata->assignment->grade);
+ if ($gradinginstance = $this->use_advanced_grading()) {
+ $gradinginstance->get_controller()->set_grade_range($grademenu);
+ $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
} else {
// use simple direct grading
- $grademenu = make_grades_menu($this->_customdata->assignment->grade);
$grademenu['-1'] = get_string('nograde');
$mform->addElement('select', 'xgrade', get_string('grade').':', $grademenu, $attributes);
Please sign in to comment.
Something went wrong with that request. Please try again.