diff --git a/grade/grading/form/lib.php b/grade/grading/form/lib.php
index f8e3b6100da..641baf394ce 100644
--- a/grade/grading/form/lib.php
+++ b/grade/grading/form/lib.php
@@ -287,65 +287,92 @@ abstract class gradingform_controller {
}
/**
- * Makes sure there is a form instance for the given rater grading the given item
+ * Returns the ACTIVE instance for this definition for the specified $raterid and $itemid
+ * (if multiple raters are allowed, or only for $itemid otherwise).
*
- * Plugins will probably override/extend this and load additional data of how their
- * forms are filled in one complex query.
- *
- * @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
- */
- abstract public function to_html($gradingformelement);
-
- /**
+ * Returns list of active instances for the specified $itemid
*
+ * @param int $itemid
+ * @return array of gradingform_instance objects
*/
- public function default_validation_error_message() {
- return '';
+ 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 validate_grading_element($elementvalue, $itemid) {
- return true;
+ 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 create_instance($raterid, $itemid) {
+ global $DB;
+ // first find if there is already an active instance for this itemid
+ if ($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 +453,225 @@ abstract class gradingform_controller {
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 string $defaultcontent default string to be returned if no active grading is found
+ * @return string
+ */
+ public function render_grade($page, $itemid, $defaultcontent) {
+ return $defaultcontent;
+ }
}
+
+/**
+ * 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;
+ }
+ $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.
+ */
+ public function update($elementvalue) {
+ // TODO update timemodified at least
+ }
+
+ /**
+ * Calculates the grade to be pushed to the gradebook
+ * @return int the grade on 0-100 scale
+ */
+ 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
+ * @return int the grade on 0-100 scale
+ */
+ public function submit_and_get_grade($elementvalue) {
+ $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.
+ * 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
+ * from 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 '';
+ }
+}
\ No newline at end of file
diff --git a/grade/grading/form/rubric/lib.php b/grade/grading/form/rubric/lib.php
index 763b94620fe..a12a55c63cf 100644
--- a/grade/grading/form/rubric/lib.php
+++ b/grade/grading/form/rubric/lib.php
@@ -266,182 +266,6 @@ class gradingform_rubric_controller extends gradingform_controller {
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
";
- //$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:
/**
@@ -495,32 +319,6 @@ class gradingform_rubric_controller extends gradingform_controller {
// 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
*
@@ -544,8 +342,8 @@ class gradingform_rubric_controller extends gradingform_controller {
// 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 +367,183 @@ class gradingform_rubric_controller extends gradingform_controller {
// 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 string $defaultcontent default string to be returned if no active grading is found
+ * @return string
+ */
+ public function render_grade($page, $itemid, $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 {
+
+ /**
+ * 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 as $criterionid => $levelid) {
+ $params = array('forminstanceid' => $instanceid, 'criterionid' => $criterionid, 'levelid' => $levelid);
+ $DB->insert_record('gradingform_rubric_fillings', $params);
+ }
+ // TODO remarks
+ 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 (!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;
+ }
+
+ /**
+ * Retrieves from DB and returns the data how this rubric was filled
+ *
+ * @return array
+ */
+ public function get_rubric_filling() {
+ // TODO cache
+ global $DB;
+ $rs = $DB->get_records('gradingform_rubric_fillings', array('forminstanceid' => $this->get_id()));
+ $grading = array();
+ foreach ($rs as $record) {
+ if ($record->levelid) {
+ $grading[$record->criterionid] = $record->levelid;
+ }
+ // TODO: remarks
+ }
+ return $grading;
+ }
+
+ /**
+ * 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.
+ */
+ public function update($data) {
+ global $DB;
+ $currentgrade = $this->get_rubric_filling();
+ parent::update($data); // TODO ? +timemodified
+ foreach ($data as $criterionid => $levelid) {
+ $params = array('forminstanceid' => $this->get_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, $data)) {
+ $params = array('forminstanceid' => $this->get_id(), 'criterionid' => $criterionid);
+ $DB->delete_records('gradingform_rubric_fillings', $params);
+ }
+ }
+ // TODO: remarks
+ }
+
+ /**
+ * Calculates the grade to be pushed to the gradebook
+ * @return int the grade on 0-100 scale
+ */
+ 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']);
+ // 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->get_controller()->get_definition()->rubric_criteria[$id]['levels'][$levelid]['score'];
+ }
+ return $curscore/$maxscore*100; // 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);
+ }
+}
\ No newline at end of file
diff --git a/grade/grading/form/rubric/renderer.php b/grade/grading/form/rubric/renderer.php
index 71b7e22c123..547e8091cd4 100644
--- a/grade/grading/form/rubric/renderer.php
+++ b/grade/grading/form/rubric/renderer.php
@@ -213,4 +213,35 @@ class gradingform_rubric_renderer {
}
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);
+ }
}
diff --git a/grade/grading/form/rubric/styles.css b/grade/grading/form/rubric/styles.css
index eed125d56bd..0168a207062 100644
--- a/grade/grading/form/rubric/styles.css
+++ b/grade/grading/form/rubric/styles.css
@@ -71,7 +71,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 */
diff --git a/lib/form/grading.php b/lib/form/grading.php
index ef11df553b1..b64ed1f7092 100644
--- a/lib/form/grading.php
+++ b/lib/form/grading.php
@@ -31,7 +31,11 @@ if (class_exists('HTML_QuickForm')) {
}
/**
- * 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 @@ class MoodleQuickForm_grading extends HTML_QuickForm_input{
$this->gradingattributes = $attributes;
}
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * Returns the input field in HTML
+ *
+ * @return string
+ */
function toHtml(){
- return $this->get_controller()->to_html($this);
- }
-
- function get_grading_attribute($name) {
- return $this->gradingattributes[$name];
- }
-
- function get_controller() {
- return $this->get_grading_attribute('controller');
+ global $PAGE;
+ return $this->get_gradinginstance()->render_grading_element($PAGE, $this);
}
/**
@@ -97,22 +116,23 @@ class MoodleQuickForm_grading extends HTML_QuickForm_input{
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);
}
}
diff --git a/mod/assignment/lib.php b/mod/assignment/lib.php
index d4096b14505..908ea30776f 100644
--- a/mod/assignment/lib.php
+++ b/mod/assignment/lib.php
@@ -264,8 +264,9 @@ class assignment_base {
* @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,12 @@ class assignment_base {
echo '