[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Grading method controller for the guide plugin 19 * 20 * @package gradingform_guide 21 * @copyright 2012 Dan Marsden <[email protected]> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once($CFG->dirroot.'/grade/grading/form/lib.php'); 28 29 /** 30 * This controller encapsulates the guide grading logic 31 * 32 * @package gradingform_guide 33 * @copyright 2012 Dan Marsden <[email protected]> 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class gradingform_guide_controller extends gradingform_controller { 37 // Modes of displaying the guide (used in gradingform_guide_renderer). 38 /** guide display mode: For editing (moderator or teacher creates a guide) */ 39 const DISPLAY_EDIT_FULL = 1; 40 /** guide display mode: Preview the guide design with hidden fields */ 41 const DISPLAY_EDIT_FROZEN = 2; 42 /** guide display mode: Preview the guide design (for person with manage permission) */ 43 const DISPLAY_PREVIEW = 3; 44 /** guide display mode: Preview the guide (for people being graded) */ 45 const DISPLAY_PREVIEW_GRADED= 8; 46 /** guide display mode: For evaluation, enabled (teacher grades a student) */ 47 const DISPLAY_EVAL = 4; 48 /** guide display mode: For evaluation, with hidden fields */ 49 const DISPLAY_EVAL_FROZEN = 5; 50 /** guide display mode: Teacher reviews filled guide */ 51 const DISPLAY_REVIEW = 6; 52 /** guide display mode: Dispaly filled guide (i.e. students see their grades) */ 53 const DISPLAY_VIEW = 7; 54 55 /** @var stdClass|false the definition structure */ 56 protected $moduleinstance = false; 57 58 /** 59 * Extends the module settings navigation with the guide grading settings 60 * 61 * This function is called when the context for the page is an activity module with the 62 * FEATURE_ADVANCED_GRADING, the user has the permission moodle/grade:managegradingforms 63 * and there is an area with the active grading method set to 'guide'. 64 * 65 * @param settings_navigation $settingsnav {@link settings_navigation} 66 * @param navigation_node $node {@link navigation_node} 67 */ 68 public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $node=null) { 69 $node->add(get_string('definemarkingguide', 'gradingform_guide'), 70 $this->get_editor_url(), settings_navigation::TYPE_CUSTOM, 71 null, null, new pix_icon('icon', '', 'gradingform_guide')); 72 } 73 74 /** 75 * Extends the module navigation 76 * 77 * This function is called when the context for the page is an activity module with the 78 * FEATURE_ADVANCED_GRADING and there is an area with the active grading method set to the given plugin. 79 * 80 * @param global_navigation $navigation {@link global_navigation} 81 * @param navigation_node $node {@link navigation_node} 82 * @return void 83 */ 84 public function extend_navigation(global_navigation $navigation, navigation_node $node=null) { 85 if (has_capability('moodle/grade:managegradingforms', $this->get_context())) { 86 // No need for preview if user can manage forms, he will have link to manage.php in settings instead. 87 return; 88 } 89 if ($this->is_form_defined() && ($options = $this->get_options()) && !empty($options['alwaysshowdefinition'])) { 90 $node->add(get_string('gradingof', 'gradingform_guide', get_grading_manager($this->get_areaid())->get_area_title()), 91 new moodle_url('/grade/grading/form/'.$this->get_method_name().'/preview.php', 92 array('areaid' => $this->get_areaid())), settings_navigation::TYPE_CUSTOM); 93 } 94 } 95 96 /** 97 * Saves the guide definition into the database 98 * 99 * @see parent::update_definition() 100 * @param stdClass $newdefinition guide definition data as coming from gradingform_guide_editguide::get_data() 101 * @param int $usermodified optional userid of the author of the definition, defaults to the current user 102 */ 103 public function update_definition(stdClass $newdefinition, $usermodified = null) { 104 $this->update_or_check_guide($newdefinition, $usermodified, true); 105 if (isset($newdefinition->guide['regrade']) && $newdefinition->guide['regrade']) { 106 $this->mark_for_regrade(); 107 } 108 } 109 110 /** 111 * Either saves the guide definition into the database or check if it has been changed. 112 * 113 * Returns the level of changes: 114 * 0 - no changes 115 * 1 - only texts or criteria sortorders are changed, students probably do not require re-grading 116 * 2 - added levels but maximum score on guide is the same, students still may not require re-grading 117 * 3 - removed criteria or changed number of points, students require re-grading but may be re-graded automatically 118 * 4 - removed levels - students require re-grading and not all students may be re-graded automatically 119 * 5 - added criteria - all students require manual re-grading 120 * 121 * @param stdClass $newdefinition guide definition data as coming from gradingform_guide_editguide::get_data() 122 * @param int|null $usermodified optional userid of the author of the definition, defaults to the current user 123 * @param bool $doupdate if true actually updates DB, otherwise performs a check 124 * @return int 125 */ 126 public function update_or_check_guide(stdClass $newdefinition, $usermodified = null, $doupdate = false) { 127 global $DB; 128 129 // Firstly update the common definition data in the {grading_definition} table. 130 if ($this->definition === false) { 131 if (!$doupdate) { 132 // If we create the new definition there is no such thing as re-grading anyway. 133 return 5; 134 } 135 // If definition does not exist yet, create a blank one 136 // (we need id to save files embedded in description). 137 parent::update_definition(new stdClass(), $usermodified); 138 parent::load_definition(); 139 } 140 if (!isset($newdefinition->guide['options'])) { 141 $newdefinition->guide['options'] = self::get_default_options(); 142 } 143 $newdefinition->options = json_encode($newdefinition->guide['options']); 144 $editoroptions = self::description_form_field_options($this->get_context()); 145 $newdefinition = file_postupdate_standard_editor($newdefinition, 'description', $editoroptions, $this->get_context(), 146 'grading', 'description', $this->definition->id); 147 148 // Reload the definition from the database. 149 $currentdefinition = $this->get_definition(true); 150 151 // Update guide data. 152 $haschanges = array(); 153 if (empty($newdefinition->guide['criteria'])) { 154 $newcriteria = array(); 155 } else { 156 $newcriteria = $newdefinition->guide['criteria']; // New ones to be saved. 157 } 158 $currentcriteria = $currentdefinition->guide_criteria; 159 $criteriafields = array('sortorder', 'description', 'descriptionformat', 'descriptionmarkers', 160 'descriptionmarkersformat', 'shortname', 'maxscore'); 161 foreach ($newcriteria as $id => $criterion) { 162 if (preg_match('/^NEWID\d+$/', $id)) { 163 // Insert criterion into DB. 164 $data = array('definitionid' => $this->definition->id, 'descriptionformat' => FORMAT_MOODLE, 165 'descriptionmarkersformat' => FORMAT_MOODLE); // TODO format is not supported yet. 166 foreach ($criteriafields as $key) { 167 if (array_key_exists($key, $criterion)) { 168 $data[$key] = $criterion[$key]; 169 } 170 } 171 if ($doupdate) { 172 $id = $DB->insert_record('gradingform_guide_criteria', $data); 173 } 174 $haschanges[5] = true; 175 } else { 176 // Update criterion in DB. 177 $data = array(); 178 foreach ($criteriafields as $key) { 179 if (array_key_exists($key, $criterion) && $criterion[$key] != $currentcriteria[$id][$key]) { 180 $data[$key] = $criterion[$key]; 181 } 182 } 183 if (!empty($data)) { 184 // Update only if something is changed. 185 $data['id'] = $id; 186 if ($doupdate) { 187 $DB->update_record('gradingform_guide_criteria', $data); 188 } 189 $haschanges[1] = true; 190 } 191 } 192 } 193 // Remove deleted criteria from DB. 194 foreach (array_keys($currentcriteria) as $id) { 195 if (!array_key_exists($id, $newcriteria)) { 196 if ($doupdate) { 197 $DB->delete_records('gradingform_guide_criteria', array('id' => $id)); 198 } 199 $haschanges[3] = true; 200 } 201 } 202 // Now handle comments. 203 if (empty($newdefinition->guide['comments'])) { 204 $newcomment = array(); 205 } else { 206 $newcomment = $newdefinition->guide['comments']; // New ones to be saved. 207 } 208 $currentcomments = $currentdefinition->guide_comments; 209 $commentfields = array('sortorder', 'description'); 210 foreach ($newcomment as $id => $comment) { 211 if (preg_match('/^NEWID\d+$/', $id)) { 212 // Insert criterion into DB. 213 $data = array('definitionid' => $this->definition->id, 'descriptionformat' => FORMAT_MOODLE); 214 foreach ($commentfields as $key) { 215 if (array_key_exists($key, $comment)) { 216 $data[$key] = $comment[$key]; 217 } 218 } 219 if ($doupdate) { 220 $id = $DB->insert_record('gradingform_guide_comments', $data); 221 } 222 } else { 223 // Update criterion in DB. 224 $data = array(); 225 foreach ($commentfields as $key) { 226 if (array_key_exists($key, $comment) && $comment[$key] != $currentcomments[$id][$key]) { 227 $data[$key] = $comment[$key]; 228 } 229 } 230 if (!empty($data)) { 231 // Update only if something is changed. 232 $data['id'] = $id; 233 if ($doupdate) { 234 $DB->update_record('gradingform_guide_comments', $data); 235 } 236 } 237 } 238 } 239 // Remove deleted criteria from DB. 240 foreach (array_keys($currentcomments) as $id) { 241 if (!array_key_exists($id, $newcomment)) { 242 if ($doupdate) { 243 $DB->delete_records('gradingform_guide_comments', array('id' => $id)); 244 } 245 } 246 } 247 // End comments handle. 248 foreach (array('status', 'description', 'descriptionformat', 'name', 'options') as $key) { 249 if (isset($newdefinition->$key) && $newdefinition->$key != $this->definition->$key) { 250 $haschanges[1] = true; 251 } 252 } 253 if ($usermodified && $usermodified != $this->definition->usermodified) { 254 $haschanges[1] = true; 255 } 256 if (!count($haschanges)) { 257 return 0; 258 } 259 if ($doupdate) { 260 parent::update_definition($newdefinition, $usermodified); 261 $this->load_definition(); 262 } 263 // Return the maximum level of changes. 264 $changelevels = array_keys($haschanges); 265 sort($changelevels); 266 return array_pop($changelevels); 267 } 268 269 /** 270 * Marks all instances filled with this guide with the status INSTANCE_STATUS_NEEDUPDATE 271 */ 272 public function mark_for_regrade() { 273 global $DB; 274 if ($this->has_active_instances()) { 275 $conditions = array('definitionid' => $this->definition->id, 276 'status' => gradingform_instance::INSTANCE_STATUS_ACTIVE); 277 $DB->set_field('grading_instances', 'status', gradingform_instance::INSTANCE_STATUS_NEEDUPDATE, $conditions); 278 } 279 } 280 281 /** 282 * Loads the guide form definition if it exists 283 * 284 * There is a new array called 'guide_criteria' appended to the list of parent's definition properties. 285 */ 286 protected function load_definition() { 287 global $DB; 288 289 // Check to see if the user prefs have changed - putting here as this function is called on post even when 290 // validation on the page fails. - hard to find a better place to locate this as it is specific to the guide. 291 $showdesc = optional_param('showmarkerdesc', null, PARAM_BOOL); // Check if we need to change pref. 292 $showdescstudent = optional_param('showstudentdesc', null, PARAM_BOOL); // Check if we need to change pref. 293 if ($showdesc !== null) { 294 set_user_preference('gradingform_guide-showmarkerdesc', $showdesc); 295 } 296 if ($showdescstudent !== null) { 297 set_user_preference('gradingform_guide-showstudentdesc', $showdescstudent); 298 } 299 300 // Get definition. 301 $definition = $DB->get_record('grading_definitions', array('areaid' => $this->areaid, 302 'method' => $this->get_method_name()), '*'); 303 if (!$definition) { 304 // The definition doesn't have to exist. It may be that we are only now creating it. 305 $this->definition = false; 306 return false; 307 } 308 309 $this->definition = $definition; 310 // Now get criteria. 311 $this->definition->guide_criteria = array(); 312 $this->definition->guide_comments = array(); 313 $criteria = $DB->get_recordset('gradingform_guide_criteria', array('definitionid' => $this->definition->id), 'sortorder'); 314 foreach ($criteria as $criterion) { 315 foreach (array('id', 'sortorder', 'description', 'descriptionformat', 316 'maxscore', 'descriptionmarkers', 'descriptionmarkersformat', 'shortname') as $fieldname) { 317 if ($fieldname == 'maxscore') { // Strip any trailing 0. 318 $this->definition->guide_criteria[$criterion->id][$fieldname] = (float)$criterion->{$fieldname}; 319 } else { 320 $this->definition->guide_criteria[$criterion->id][$fieldname] = $criterion->{$fieldname}; 321 } 322 } 323 } 324 $criteria->close(); 325 326 // Now get comments. 327 $comments = $DB->get_recordset('gradingform_guide_comments', array('definitionid' => $this->definition->id), 'sortorder'); 328 foreach ($comments as $comment) { 329 foreach (array('id', 'sortorder', 'description', 'descriptionformat') as $fieldname) { 330 $this->definition->guide_comments[$comment->id][$fieldname] = $comment->{$fieldname}; 331 } 332 } 333 $comments->close(); 334 if (empty($this->moduleinstance)) { // Only set if empty. 335 $modulename = $this->get_component(); 336 $context = $this->get_context(); 337 if (strpos($modulename, 'mod_') === 0) { 338 $dbman = $DB->get_manager(); 339 $modulename = substr($modulename, 4); 340 if ($dbman->table_exists($modulename)) { 341 $cm = get_coursemodule_from_id($modulename, $context->instanceid); 342 if (!empty($cm)) { // This should only occur when the course is being deleted. 343 $this->moduleinstance = $DB->get_record($modulename, array("id"=>$cm->instance)); 344 } 345 } 346 } 347 } 348 } 349 350 /** 351 * Returns the default options for the guide display 352 * 353 * @return array 354 */ 355 public static function get_default_options() { 356 $options = array( 357 'alwaysshowdefinition' => 1, 358 'showmarkspercriterionstudents' => 1, 359 ); 360 return $options; 361 } 362 363 /** 364 * Gets the options of this guide definition, fills the missing options with default values 365 * 366 * @return array 367 */ 368 public function get_options() { 369 $options = self::get_default_options(); 370 if (!empty($this->definition->options)) { 371 $thisoptions = json_decode($this->definition->options); 372 foreach ($thisoptions as $option => $value) { 373 $options[$option] = $value; 374 } 375 } 376 return $options; 377 } 378 379 /** 380 * Converts the current definition into an object suitable for the editor form's set_data() 381 * 382 * @param bool $addemptycriterion whether to add an empty criterion if the guide is completely empty (just being created) 383 * @return stdClass 384 */ 385 public function get_definition_for_editing($addemptycriterion = false) { 386 387 $definition = $this->get_definition(); 388 $properties = new stdClass(); 389 $properties->areaid = $this->areaid; 390 if (isset($this->moduleinstance->grade)) { 391 $properties->modulegrade = $this->moduleinstance->grade; 392 } 393 if ($definition) { 394 foreach (array('id', 'name', 'description', 'descriptionformat', 'status') as $key) { 395 $properties->$key = $definition->$key; 396 } 397 $options = self::description_form_field_options($this->get_context()); 398 $properties = file_prepare_standard_editor($properties, 'description', $options, $this->get_context(), 399 'grading', 'description', $definition->id); 400 } 401 $properties->guide = array('criteria' => array(), 'options' => $this->get_options(), 'comments' => array()); 402 if (!empty($definition->guide_criteria)) { 403 $properties->guide['criteria'] = $definition->guide_criteria; 404 } else if (!$definition && $addemptycriterion) { 405 $properties->guide['criteria'] = array('addcriterion' => 1); 406 } 407 if (!empty($definition->guide_comments)) { 408 $properties->guide['comments'] = $definition->guide_comments; 409 } else if (!$definition && $addemptycriterion) { 410 $properties->guide['comments'] = array('addcomment' => 1); 411 } 412 return $properties; 413 } 414 415 /** 416 * Returns the form definition suitable for cloning into another area 417 * 418 * @see parent::get_definition_copy() 419 * @param gradingform_controller $target the controller of the new copy 420 * @return stdClass definition structure to pass to the target's {@link update_definition()} 421 */ 422 public function get_definition_copy(gradingform_controller $target) { 423 424 $new = parent::get_definition_copy($target); 425 $old = $this->get_definition_for_editing(); 426 $new->description_editor = $old->description_editor; 427 $new->guide = array('criteria' => array(), 'options' => $old->guide['options'], 'comments' => array()); 428 $newcritid = 1; 429 foreach ($old->guide['criteria'] as $oldcritid => $oldcrit) { 430 unset($oldcrit['id']); 431 $new->guide['criteria']['NEWID'.$newcritid] = $oldcrit; 432 $newcritid++; 433 } 434 $newcomid = 1; 435 foreach ($old->guide['comments'] as $oldcritid => $oldcom) { 436 unset($oldcom['id']); 437 $new->guide['comments']['NEWID'.$newcomid] = $oldcom; 438 $newcomid++; 439 } 440 return $new; 441 } 442 443 /** 444 * Options for displaying the guide description field in the form 445 * 446 * @param context $context 447 * @return array options for the form description field 448 */ 449 public static function description_form_field_options($context) { 450 global $CFG; 451 return array( 452 'maxfiles' => -1, 453 'maxbytes' => get_max_upload_file_size($CFG->maxbytes), 454 'context' => $context, 455 ); 456 } 457 458 /** 459 * Formats the definition description for display on page 460 * 461 * @return string 462 */ 463 public function get_formatted_description() { 464 if (!isset($this->definition->description)) { 465 return ''; 466 } 467 $context = $this->get_context(); 468 469 $options = self::description_form_field_options($this->get_context()); 470 $description = file_rewrite_pluginfile_urls($this->definition->description, 'pluginfile.php', $context->id, 471 'grading', 'description', $this->definition->id, $options); 472 473 $formatoptions = array( 474 'noclean' => false, 475 'trusted' => false, 476 'filter' => true, 477 'context' => $context 478 ); 479 return format_text($description, $this->definition->descriptionformat, $formatoptions); 480 } 481 482 /** 483 * Returns the guide plugin renderer 484 * 485 * @param moodle_page $page the target page 486 * @return gradingform_guide_renderer 487 */ 488 public function get_renderer(moodle_page $page) { 489 return $page->get_renderer('gradingform_'. $this->get_method_name()); 490 } 491 492 /** 493 * Returns the HTML code displaying the preview of the grading form 494 * 495 * @param moodle_page $page the target page 496 * @return string 497 */ 498 public function render_preview(moodle_page $page) { 499 500 if (!$this->is_form_defined()) { 501 throw new coding_exception('It is the caller\'s responsibility to make sure that the form is actually defined'); 502 } 503 504 // Check if current user is able to see preview 505 $options = $this->get_options(); 506 if (empty($options['alwaysshowdefinition']) && !has_capability('moodle/grade:managegradingforms', $page->context)) { 507 return ''; 508 } 509 510 $criteria = $this->definition->guide_criteria; 511 $comments = $this->definition->guide_comments; 512 $output = $this->get_renderer($page); 513 514 $guide = ''; 515 $guide .= $output->box($this->get_formatted_description(), 'gradingform_guide-description'); 516 if (has_capability('moodle/grade:managegradingforms', $page->context)) { 517 $guide .= $output->display_guide_mapping_explained($this->get_min_max_score()); 518 $guide .= $output->display_guide($criteria, $comments, $options, self::DISPLAY_PREVIEW, 'guide'); 519 } else { 520 $guide .= $output->display_guide($criteria, $comments, $options, self::DISPLAY_PREVIEW_GRADED, 'guide'); 521 } 522 523 return $guide; 524 } 525 526 /** 527 * Deletes the guide definition and all the associated information 528 */ 529 protected function delete_plugin_definition() { 530 global $DB; 531 532 // Get the list of instances. 533 $instances = array_keys($DB->get_records('grading_instances', array('definitionid' => $this->definition->id), '', 'id')); 534 // Delete all fillings. 535 $DB->delete_records_list('gradingform_guide_fillings', 'instanceid', $instances); 536 // Delete instances. 537 $DB->delete_records_list('grading_instances', 'id', $instances); 538 // Get the list of criteria records. 539 $criteria = array_keys($DB->get_records('gradingform_guide_criteria', 540 array('definitionid' => $this->definition->id), '', 'id')); 541 // Delete critera. 542 $DB->delete_records_list('gradingform_guide_criteria', 'id', $criteria); 543 // Delete comments. 544 $DB->delete_records('gradingform_guide_comments', array('definitionid' => $this->definition->id)); 545 } 546 547 /** 548 * If instanceid is specified and grading instance exists and it is created by this rater for 549 * this item, this instance is returned. 550 * If there exists a draft for this raterid+itemid, take this draft (this is the change from parent) 551 * Otherwise new instance is created for the specified rater and itemid 552 * 553 * @param int $instanceid 554 * @param int $raterid 555 * @param int $itemid 556 * @return gradingform_instance 557 */ 558 public function get_or_create_instance($instanceid, $raterid, $itemid) { 559 global $DB; 560 if ($instanceid && 561 $instance = $DB->get_record('grading_instances', 562 array('id' => $instanceid, 'raterid' => $raterid, 'itemid' => $itemid), '*', IGNORE_MISSING)) { 563 return $this->get_instance($instance); 564 } 565 if ($itemid && $raterid) { 566 if ($rs = $DB->get_records('grading_instances', array('raterid' => $raterid, 'itemid' => $itemid), 567 'timemodified DESC', '*', 0, 1)) { 568 $record = reset($rs); 569 $currentinstance = $this->get_current_instance($raterid, $itemid); 570 if ($record->status == gradingform_guide_instance::INSTANCE_STATUS_INCOMPLETE && 571 (!$currentinstance || $record->timemodified > $currentinstance->get_data('timemodified'))) { 572 $record->isrestored = true; 573 return $this->get_instance($record); 574 } 575 } 576 } 577 return $this->create_instance($raterid, $itemid); 578 } 579 580 /** 581 * Returns html code to be included in student's feedback. 582 * 583 * @param moodle_page $page 584 * @param int $itemid 585 * @param array $gradinginfo result of function grade_get_grades 586 * @param string $defaultcontent default string to be returned if no active grading is found 587 * @param bool $cangrade whether current user has capability to grade in this context 588 * @return string 589 */ 590 public function render_grade($page, $itemid, $gradinginfo, $defaultcontent, $cangrade) { 591 return $this->get_renderer($page)->display_instances($this->get_active_instances($itemid), $defaultcontent, $cangrade); 592 } 593 594 // Full-text search support. 595 596 /** 597 * Prepare the part of the search query to append to the FROM statement 598 * 599 * @param string $gdid the alias of grading_definitions.id column used by the caller 600 * @return string 601 */ 602 public static function sql_search_from_tables($gdid) { 603 return " LEFT JOIN {gradingform_guide_criteria} gc ON (gc.definitionid = $gdid)"; 604 } 605 606 /** 607 * Prepare the parts of the SQL WHERE statement to search for the given token 608 * 609 * The returned array cosists of the list of SQL comparions and the list of 610 * respective parameters for the comparisons. The returned chunks will be joined 611 * with other conditions using the OR operator. 612 * 613 * @param string $token token to search for 614 * @return array An array containing two more arrays 615 * Array of search SQL fragments 616 * Array of params for the search fragments 617 */ 618 public static function sql_search_where($token) { 619 global $DB; 620 621 $subsql = array(); 622 $params = array(); 623 624 // Search in guide criteria description. 625 $subsql[] = $DB->sql_like('gc.description', '?', false, false); 626 $params[] = '%'.$DB->sql_like_escape($token).'%'; 627 628 return array($subsql, $params); 629 } 630 631 /** 632 * Calculates and returns the possible minimum and maximum score (in points) for this guide 633 * 634 * @return array 635 */ 636 public function get_min_max_score() { 637 if (!$this->is_form_available()) { 638 return null; 639 } 640 $returnvalue = array('minscore' => 0, 'maxscore' => 0); 641 $maxscore = 0; 642 foreach ($this->get_definition()->guide_criteria as $id => $criterion) { 643 $maxscore += $criterion['maxscore']; 644 } 645 $returnvalue['maxscore'] = $maxscore; 646 $returnvalue['minscore'] = 0; 647 if (!empty($this->moduleinstance->grade)) { 648 $graderange = make_grades_menu($this->moduleinstance->grade); 649 $returnvalue['modulegrade'] = count($graderange) - 1; 650 } 651 return $returnvalue; 652 } 653 654 /** 655 * @return array An array containing 2 key/value pairs which hold the external_multiple_structure 656 * for the 'guide_criteria' and the 'guide_comments'. 657 * @see gradingform_controller::get_external_definition_details() 658 * @since Moodle 2.5 659 */ 660 public static function get_external_definition_details() { 661 $guide_criteria = new external_multiple_structure( 662 new external_single_structure( 663 array( 664 'id' => new external_value(PARAM_INT, 'criterion id', VALUE_OPTIONAL), 665 'sortorder' => new external_value(PARAM_INT, 'sortorder', VALUE_OPTIONAL), 666 'description' => new external_value(PARAM_RAW, 'description', VALUE_OPTIONAL), 667 'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL), 668 'shortname' => new external_value(PARAM_TEXT, 'description'), 669 'descriptionmarkers' => new external_value(PARAM_RAW, 'markers description', VALUE_OPTIONAL), 670 'descriptionmarkersformat' => new external_format_value('descriptionmarkers', VALUE_OPTIONAL), 671 'maxscore' => new external_value(PARAM_FLOAT, 'maximum score') 672 ) 673 ) 674 ); 675 $guide_comments = new external_multiple_structure( 676 new external_single_structure( 677 array( 678 'id' => new external_value(PARAM_INT, 'criterion id', VALUE_OPTIONAL), 679 'sortorder' => new external_value(PARAM_INT, 'sortorder', VALUE_OPTIONAL), 680 'description' => new external_value(PARAM_RAW, 'description', VALUE_OPTIONAL), 681 'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL) 682 ) 683 ), 'comments', VALUE_OPTIONAL 684 ); 685 return array('guide_criteria' => $guide_criteria, 'guide_comments' => $guide_comments); 686 } 687 688 /** 689 * Returns an array that defines the structure of the guide's filling. This function is used by 690 * the web service function core_grading_external::get_gradingform_instances(). 691 * 692 * @return An array containing a single key/value pair with the 'criteria' external_multiple_structure 693 * @see gradingform_controller::get_external_instance_filling_details() 694 * @since Moodle 2.6 695 */ 696 public static function get_external_instance_filling_details() { 697 $criteria = new external_multiple_structure( 698 new external_single_structure( 699 array( 700 'id' => new external_value(PARAM_INT, 'filling id'), 701 'criterionid' => new external_value(PARAM_INT, 'criterion id'), 702 'levelid' => new external_value(PARAM_INT, 'level id', VALUE_OPTIONAL), 703 'remark' => new external_value(PARAM_RAW, 'remark', VALUE_OPTIONAL), 704 'remarkformat' => new external_format_value('remark', VALUE_OPTIONAL), 705 'score' => new external_value(PARAM_FLOAT, 'maximum score') 706 ) 707 ), 'filling', VALUE_OPTIONAL 708 ); 709 return array ('criteria' => $criteria); 710 } 711 712 } 713 714 /** 715 * Class to manage one guide grading instance. Stores information and performs actions like 716 * update, copy, validate, submit, etc. 717 * 718 * @package gradingform_guide 719 * @copyright 2012 Dan Marsden <[email protected]> 720 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 721 */ 722 class gradingform_guide_instance extends gradingform_instance { 723 724 /** @var array */ 725 protected $guide; 726 727 /** @var array An array of validation errors */ 728 protected $validationerrors = array(); 729 730 /** 731 * Deletes this (INCOMPLETE) instance from database. 732 */ 733 public function cancel() { 734 global $DB; 735 parent::cancel(); 736 $DB->delete_records('gradingform_guide_fillings', array('instanceid' => $this->get_id())); 737 } 738 739 /** 740 * Duplicates the instance before editing (optionally substitutes raterid and/or itemid with 741 * the specified values) 742 * 743 * @param int $raterid value for raterid in the duplicate 744 * @param int $itemid value for itemid in the duplicate 745 * @return int id of the new instance 746 */ 747 public function copy($raterid, $itemid) { 748 global $DB; 749 $instanceid = parent::copy($raterid, $itemid); 750 $currentgrade = $this->get_guide_filling(); 751 foreach ($currentgrade['criteria'] as $criterionid => $record) { 752 $params = array('instanceid' => $instanceid, 'criterionid' => $criterionid, 753 'score' => $record['score'], 'remark' => $record['remark'], 'remarkformat' => $record['remarkformat']); 754 $DB->insert_record('gradingform_guide_fillings', $params); 755 } 756 return $instanceid; 757 } 758 759 /** 760 * Validates that guide is fully completed and contains valid grade on each criterion 761 * 762 * @param array $elementvalue value of element as came in form submit 763 * @return boolean true if the form data is validated and contains no errors 764 */ 765 public function validate_grading_element($elementvalue) { 766 $criteria = $this->get_controller()->get_definition()->guide_criteria; 767 if (!isset($elementvalue['criteria']) || !is_array($elementvalue['criteria']) || 768 count($elementvalue['criteria']) < count($criteria)) { 769 return false; 770 } 771 // Reset validation errors. 772 $this->validationerrors = null; 773 foreach ($criteria as $id => $criterion) { 774 if (!isset($elementvalue['criteria'][$id]['score']) 775 || $criterion['maxscore'] < $elementvalue['criteria'][$id]['score'] 776 || !is_numeric($elementvalue['criteria'][$id]['score']) 777 || $elementvalue['criteria'][$id]['score'] < 0) { 778 $this->validationerrors[$id]['score'] = $elementvalue['criteria'][$id]['score']; 779 } 780 } 781 if (!empty($this->validationerrors)) { 782 return false; 783 } 784 return true; 785 } 786 787 /** 788 * Retrieves from DB and returns the data how this guide was filled 789 * 790 * @param bool $force whether to force DB query even if the data is cached 791 * @return array 792 */ 793 public function get_guide_filling($force = false) { 794 global $DB; 795 if ($this->guide === null || $force) { 796 $records = $DB->get_records('gradingform_guide_fillings', array('instanceid' => $this->get_id())); 797 $this->guide = array('criteria' => array()); 798 foreach ($records as $record) { 799 $record->score = (float)$record->score; // Strip trailing 0. 800 $this->guide['criteria'][$record->criterionid] = (array)$record; 801 } 802 } 803 return $this->guide; 804 } 805 806 /** 807 * Updates the instance with the data received from grading form. This function may be 808 * called via AJAX when grading is not yet completed, so it does not change the 809 * status of the instance. 810 * 811 * @param array $data 812 */ 813 public function update($data) { 814 global $DB; 815 $currentgrade = $this->get_guide_filling(); 816 parent::update($data); 817 818 foreach ($data['criteria'] as $criterionid => $record) { 819 if (!array_key_exists($criterionid, $currentgrade['criteria'])) { 820 $newrecord = array('instanceid' => $this->get_id(), 'criterionid' => $criterionid, 821 'score' => $record['score'], 'remarkformat' => FORMAT_MOODLE); 822 if (isset($record['remark'])) { 823 $newrecord['remark'] = $record['remark']; 824 } 825 $DB->insert_record('gradingform_guide_fillings', $newrecord); 826 } else { 827 $newrecord = array('id' => $currentgrade['criteria'][$criterionid]['id']); 828 foreach (array('score', 'remark'/*, 'remarkformat' TODO */) as $key) { 829 if (isset($record[$key]) && $currentgrade['criteria'][$criterionid][$key] != $record[$key]) { 830 $newrecord[$key] = $record[$key]; 831 } 832 } 833 if (count($newrecord) > 1) { 834 $DB->update_record('gradingform_guide_fillings', $newrecord); 835 } 836 } 837 } 838 foreach ($currentgrade['criteria'] as $criterionid => $record) { 839 if (!array_key_exists($criterionid, $data['criteria'])) { 840 $DB->delete_records('gradingform_guide_fillings', array('id' => $record['id'])); 841 } 842 } 843 $this->get_guide_filling(true); 844 } 845 846 /** 847 * Calculates the grade to be pushed to the gradebook 848 * 849 * @return float|int the valid grade from $this->get_controller()->get_grade_range() 850 */ 851 public function get_grade() { 852 $grade = $this->get_guide_filling(); 853 854 if (!($scores = $this->get_controller()->get_min_max_score()) || $scores['maxscore'] <= $scores['minscore']) { 855 return -1; 856 } 857 858 $graderange = array_keys($this->get_controller()->get_grade_range()); 859 if (empty($graderange)) { 860 return -1; 861 } 862 sort($graderange); 863 $mingrade = $graderange[0]; 864 $maxgrade = $graderange[count($graderange) - 1]; 865 866 $curscore = 0; 867 foreach ($grade['criteria'] as $record) { 868 $curscore += $record['score']; 869 } 870 $gradeoffset = ($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])* 871 ($maxgrade-$mingrade); 872 if ($this->get_controller()->get_allow_grade_decimals()) { 873 return $gradeoffset + $mingrade; 874 } 875 return round($gradeoffset, 0) + $mingrade; 876 } 877 878 /** 879 * Returns html for form element of type 'grading'. 880 * 881 * @param moodle_page $page 882 * @param MoodleQuickForm_grading $gradingformelement 883 * @return string 884 */ 885 public function render_grading_element($page, $gradingformelement) { 886 if (!$gradingformelement->_flagFrozen) { 887 $module = array('name'=>'gradingform_guide', 'fullpath'=>'/grade/grading/form/guide/js/guide.js'); 888 $page->requires->js_init_call('M.gradingform_guide.init', array( 889 array('name' => $gradingformelement->getName())), true, $module); 890 $mode = gradingform_guide_controller::DISPLAY_EVAL; 891 } else { 892 if ($gradingformelement->_persistantFreeze) { 893 $mode = gradingform_guide_controller::DISPLAY_EVAL_FROZEN; 894 } else { 895 $mode = gradingform_guide_controller::DISPLAY_REVIEW; 896 } 897 } 898 $criteria = $this->get_controller()->get_definition()->guide_criteria; 899 $comments = $this->get_controller()->get_definition()->guide_comments; 900 $options = $this->get_controller()->get_options(); 901 $value = $gradingformelement->getValue(); 902 $html = ''; 903 if ($value === null) { 904 $value = $this->get_guide_filling(); 905 } else if (!$this->validate_grading_element($value)) { 906 $html .= html_writer::tag('div', get_string('guidenotcompleted', 'gradingform_guide'), 907 array('class' => 'gradingform_guide-error')); 908 if (!empty($this->validationerrors)) { 909 foreach ($this->validationerrors as $id => $err) { 910 $a = new stdClass(); 911 $a->criterianame = s($criteria[$id]['shortname']); 912 $a->maxscore = $criteria[$id]['maxscore']; 913 $html .= html_writer::tag('div', get_string('err_scoreinvalid', 'gradingform_guide', $a), 914 array('class' => 'gradingform_guide-error')); 915 } 916 } 917 } 918 $currentinstance = $this->get_current_instance(); 919 if ($currentinstance && $currentinstance->get_status() == gradingform_instance::INSTANCE_STATUS_NEEDUPDATE) { 920 $html .= html_writer::tag('div', get_string('needregrademessage', 'gradingform_guide'), 921 array('class' => 'gradingform_guide-regrade')); 922 } 923 $haschanges = false; 924 if ($currentinstance) { 925 $curfilling = $currentinstance->get_guide_filling(); 926 foreach ($curfilling['criteria'] as $criterionid => $curvalues) { 927 $value['criteria'][$criterionid]['score'] = $curvalues['score']; 928 $newremark = null; 929 $newscore = null; 930 if (isset($value['criteria'][$criterionid]['remark'])) { 931 $newremark = $value['criteria'][$criterionid]['remark']; 932 } 933 if (isset($value['criteria'][$criterionid]['score'])) { 934 $newscore = $value['criteria'][$criterionid]['score']; 935 } 936 if ($newscore != $curvalues['score'] || $newremark != $curvalues['remark']) { 937 $haschanges = true; 938 } 939 } 940 } 941 if ($this->get_data('isrestored') && $haschanges) { 942 $html .= html_writer::tag('div', get_string('restoredfromdraft', 'gradingform_guide'), 943 array('class' => 'gradingform_guide-restored')); 944 } 945 $html .= html_writer::tag('div', $this->get_controller()->get_formatted_description(), 946 array('class' => 'gradingform_guide-description')); 947 $html .= $this->get_controller()->get_renderer($page)->display_guide($criteria, $comments, $options, $mode, 948 $gradingformelement->getName(), $value, $this->validationerrors); 949 return $html; 950 } 951 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |