[ Index ] |
PHP Cross Reference of Unnamed Project |
[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 * Advanced grading methods support 19 * 20 * @package core_grading 21 * @copyright 2011 David Mudrak <david@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 /** 28 * Factory method returning an instance of the grading manager 29 * 30 * There are basically ways how to use this factory method. If the area record 31 * id is known to the caller, get the manager for that area by providing just 32 * the id. If the area record id is not know, the context, component and area name 33 * can be provided. Note that null values are allowed in the second case as the context, 34 * component and the area name can be set explicitly later. 35 * 36 * @category grading 37 * @example $manager = get_grading_manager($areaid); 38 * @example $manager = get_grading_manager(context_system::instance()); 39 * @example $manager = get_grading_manager($context, 'mod_assignment', 'submission'); 40 * @param stdClass|int|null $context_or_areaid if $areaid is passed, no other parameter is needed 41 * @param string|null $component the frankenstyle name of the component 42 * @param string|null $area the name of the gradable area 43 * @return grading_manager 44 */ 45 function get_grading_manager($context_or_areaid = null, $component = null, $area = null) { 46 global $DB; 47 48 $manager = new grading_manager(); 49 50 if (is_object($context_or_areaid)) { 51 $context = $context_or_areaid; 52 } else { 53 $context = null; 54 55 if (is_numeric($context_or_areaid)) { 56 $manager->load($context_or_areaid); 57 return $manager; 58 } 59 } 60 61 if (!is_null($context)) { 62 $manager->set_context($context); 63 } 64 65 if (!is_null($component)) { 66 $manager->set_component($component); 67 } 68 69 if (!is_null($area)) { 70 $manager->set_area($area); 71 } 72 73 return $manager; 74 } 75 76 /** 77 * General class providing access to common grading features 78 * 79 * Grading manager provides access to the particular grading method controller 80 * in that area. 81 * 82 * Fully initialized instance of the grading manager operates over a single 83 * gradable area. It is possible to work with a partially initialized manager 84 * that knows just context and component without known area, for example. 85 * It is also possible to change context, component and area of an existing 86 * manager. Such pattern is used when copying form definitions, for example. 87 * 88 * @package core_grading 89 * @copyright 2011 David Mudrak <david@moodle.com> 90 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 91 * @category grading 92 */ 93 class grading_manager { 94 95 /** @var stdClass the context */ 96 protected $context; 97 98 /** @var string the frankenstyle name of the component */ 99 protected $component; 100 101 /** @var string the name of the gradable area */ 102 protected $area; 103 104 /** @var stdClass|false|null the raw record from {grading_areas}, false if does not exist, null if invalidated cache */ 105 private $areacache = null; 106 107 /** 108 * Returns grading manager context 109 * 110 * @return stdClass grading manager context 111 */ 112 public function get_context() { 113 return $this->context; 114 } 115 116 /** 117 * Sets the context the manager operates on 118 * 119 * @param stdClass $context 120 */ 121 public function set_context(stdClass $context) { 122 $this->areacache = null; 123 $this->context = $context; 124 } 125 126 /** 127 * Returns grading manager component 128 * 129 * @return string grading manager component 130 */ 131 public function get_component() { 132 return $this->component; 133 } 134 135 /** 136 * Sets the component the manager operates on 137 * 138 * @param string $component the frankenstyle name of the component 139 */ 140 public function set_component($component) { 141 $this->areacache = null; 142 list($type, $name) = core_component::normalize_component($component); 143 $this->component = $type.'_'.$name; 144 } 145 146 /** 147 * Returns grading manager area name 148 * 149 * @return string grading manager area name 150 */ 151 public function get_area() { 152 return $this->area; 153 } 154 155 /** 156 * Sets the area the manager operates on 157 * 158 * @param string $area the name of the gradable area 159 */ 160 public function set_area($area) { 161 $this->areacache = null; 162 $this->area = $area; 163 } 164 165 /** 166 * Returns a text describing the context and the component 167 * 168 * At the moment this works for gradable areas in course modules. In the future, this 169 * method should be improved so it works for other contexts (blocks, gradebook items etc) 170 * or subplugins. 171 * 172 * @return string 173 */ 174 public function get_component_title() { 175 176 $this->ensure_isset(array('context', 'component')); 177 178 if ($this->get_context()->contextlevel == CONTEXT_SYSTEM) { 179 if ($this->get_component() == 'core_grading') { 180 $title = ''; // we are in the bank UI 181 } else { 182 throw new coding_exception('Unsupported component at the system context'); 183 } 184 185 } else if ($this->get_context()->contextlevel >= CONTEXT_COURSE) { 186 list($context, $course, $cm) = get_context_info_array($this->get_context()->id); 187 188 if (strval($cm->name) !== '') { 189 $title = $cm->name; 190 } else { 191 debugging('Gradable areas are currently supported at the course module level only', DEBUG_DEVELOPER); 192 $title = $this->get_component(); 193 } 194 195 } else { 196 throw new coding_exception('Unsupported gradable area context level'); 197 } 198 199 return $title; 200 } 201 202 /** 203 * Returns the localized title of the currently set area 204 * 205 * @return string 206 */ 207 public function get_area_title() { 208 209 if ($this->get_context()->contextlevel == CONTEXT_SYSTEM) { 210 return ''; 211 212 } else if ($this->get_context()->contextlevel >= CONTEXT_COURSE) { 213 $this->ensure_isset(array('context', 'component', 'area')); 214 $areas = $this->get_available_areas(); 215 if (array_key_exists($this->get_area(), $areas)) { 216 return $areas[$this->get_area()]; 217 } else { 218 debugging('Unknown area!'); 219 return '???'; 220 } 221 222 } else { 223 throw new coding_exception('Unsupported context level'); 224 } 225 } 226 227 /** 228 * Loads the gradable area info from the database 229 * 230 * @param int $areaid 231 */ 232 public function load($areaid) { 233 global $DB; 234 235 $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST); 236 $this->context = context::instance_by_id($this->areacache->contextid, MUST_EXIST); 237 $this->component = $this->areacache->component; 238 $this->area = $this->areacache->areaname; 239 } 240 241 /** 242 * Returns the list of installed grading plugins together, optionally extended 243 * with a simple direct grading. 244 * 245 * @param bool $includenone should the 'Simple direct grading' be included 246 * @return array of the (string)name => (string)localized title of the method 247 */ 248 public static function available_methods($includenone = true) { 249 250 if ($includenone) { 251 $list = array('' => get_string('gradingmethodnone', 'core_grading')); 252 } else { 253 $list = array(); 254 } 255 256 foreach (core_component::get_plugin_list('gradingform') as $name => $location) { 257 $list[$name] = get_string('pluginname', 'gradingform_'.$name); 258 } 259 260 return $list; 261 } 262 263 /** 264 * Returns the list of available grading methods in the given context 265 * 266 * Currently this is just a static list obtained from {@link self::available_methods()}. 267 * In the future, the list of available methods may be controlled per-context. 268 * 269 * Requires the context property to be set in advance. 270 * 271 * @param bool $includenone should the 'Simple direct grading' be included 272 * @return array of the (string)name => (string)localized title of the method 273 */ 274 public function get_available_methods($includenone = true) { 275 $this->ensure_isset(array('context')); 276 return self::available_methods($includenone); 277 } 278 279 /** 280 * Returns the list of gradable areas provided by the given component 281 * 282 * This performs a callback to the library of the relevant plugin to obtain 283 * the list of supported areas. 284 * 285 * @param string $component normalized component name 286 * @return array of (string)areacode => (string)localized title of the area 287 */ 288 public static function available_areas($component) { 289 global $CFG; 290 291 list($plugintype, $pluginname) = core_component::normalize_component($component); 292 293 if ($component === 'core_grading') { 294 return array(); 295 296 } else if ($plugintype === 'mod') { 297 return plugin_callback('mod', $pluginname, 'grading', 'areas_list', null, array()); 298 299 } else { 300 throw new coding_exception('Unsupported area location'); 301 } 302 } 303 304 305 /** 306 * Returns the list of gradable areas in the given context and component 307 * 308 * This performs a callback to the library of the relevant plugin to obtain 309 * the list of supported areas. 310 * @return array of (string)areacode => (string)localized title of the area 311 */ 312 public function get_available_areas() { 313 global $CFG; 314 315 $this->ensure_isset(array('context', 'component')); 316 317 if ($this->get_context()->contextlevel == CONTEXT_SYSTEM) { 318 if ($this->get_component() !== 'core_grading') { 319 throw new coding_exception('Unsupported component at the system context'); 320 } else { 321 return array(); 322 } 323 324 } else if ($this->get_context()->contextlevel == CONTEXT_MODULE) { 325 list($context, $course, $cm) = get_context_info_array($this->get_context()->id); 326 return self::available_areas('mod_'.$cm->modname); 327 328 } else { 329 throw new coding_exception('Unsupported gradable area context level'); 330 } 331 } 332 333 /** 334 * Returns the currently active grading method in the gradable area 335 * 336 * @return string|null the name of the grading plugin of null if it has not been set 337 */ 338 public function get_active_method() { 339 global $DB; 340 341 $this->ensure_isset(array('context', 'component', 'area')); 342 343 // get the current grading area record if it exists 344 if (is_null($this->areacache)) { 345 $this->areacache = $DB->get_record('grading_areas', array( 346 'contextid' => $this->context->id, 347 'component' => $this->component, 348 'areaname' => $this->area), 349 '*', IGNORE_MISSING); 350 } 351 352 if ($this->areacache === false) { 353 // no area record yet 354 return null; 355 } 356 357 return $this->areacache->activemethod; 358 } 359 360 /** 361 * Sets the currently active grading method in the gradable area 362 * 363 * @param string $method the method name, eg 'rubric' (must be available) 364 * @return bool true if the method changed or was just set, false otherwise 365 */ 366 public function set_active_method($method) { 367 global $DB; 368 369 $this->ensure_isset(array('context', 'component', 'area')); 370 371 // make sure the passed method is empty or a valid plugin name 372 if (empty($method)) { 373 $method = null; 374 } else { 375 if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) { 376 throw new moodle_exception('invalid_method_name', 'core_grading'); 377 } 378 $available = $this->get_available_methods(false); 379 if (!array_key_exists($method, $available)) { 380 throw new moodle_exception('invalid_method_name', 'core_grading'); 381 } 382 } 383 384 // get the current grading area record if it exists 385 if (is_null($this->areacache)) { 386 $this->areacache = $DB->get_record('grading_areas', array( 387 'contextid' => $this->context->id, 388 'component' => $this->component, 389 'areaname' => $this->area), 390 '*', IGNORE_MISSING); 391 } 392 393 $methodchanged = false; 394 395 if ($this->areacache === false) { 396 // no area record yet, create one with the active method set 397 $area = array( 398 'contextid' => $this->context->id, 399 'component' => $this->component, 400 'areaname' => $this->area, 401 'activemethod' => $method); 402 $DB->insert_record('grading_areas', $area); 403 $methodchanged = true; 404 405 } else { 406 // update the existing record if needed 407 if ($this->areacache->activemethod !== $method) { 408 $DB->set_field('grading_areas', 'activemethod', $method, array('id' => $this->areacache->id)); 409 $methodchanged = true; 410 } 411 } 412 413 $this->areacache = null; 414 415 return $methodchanged; 416 } 417 418 /** 419 * Extends the settings navigation with the grading settings 420 * 421 * This function is called when the context for the page is an activity module with the 422 * FEATURE_ADVANCED_GRADING and the user has the permission moodle/grade:managegradingforms. 423 * 424 * @param settings_navigation $settingsnav {@link settings_navigation} 425 * @param navigation_node $modulenode {@link navigation_node} 426 */ 427 public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $modulenode=null) { 428 429 $this->ensure_isset(array('context', 'component')); 430 431 $areas = $this->get_available_areas(); 432 433 if (empty($areas)) { 434 // no money, no funny 435 return; 436 437 } else if (count($areas) == 1) { 438 // make just a single node for the management screen 439 $areatitle = reset($areas); 440 $areaname = key($areas); 441 $this->set_area($areaname); 442 $method = $this->get_active_method(); 443 $managementnode = $modulenode->add(get_string('gradingmanagement', 'core_grading'), 444 $this->get_management_url(), settings_navigation::TYPE_CUSTOM); 445 if ($method) { 446 $controller = $this->get_controller($method); 447 $controller->extend_settings_navigation($settingsnav, $managementnode); 448 } 449 450 } else { 451 // make management screen node for each area 452 $managementnode = $modulenode->add(get_string('gradingmanagement', 'core_grading'), 453 null, settings_navigation::TYPE_CUSTOM); 454 foreach ($areas as $areaname => $areatitle) { 455 $this->set_area($areaname); 456 $method = $this->get_active_method(); 457 $node = $managementnode->add($areatitle, 458 $this->get_management_url(), settings_navigation::TYPE_CUSTOM); 459 if ($method) { 460 $controller = $this->get_controller($method); 461 $controller->extend_settings_navigation($settingsnav, $node); 462 } 463 } 464 } 465 } 466 467 /** 468 * Extends the module navigation with the advanced grading information 469 * 470 * This function is called when the context for the page is an activity module with the 471 * FEATURE_ADVANCED_GRADING. 472 * 473 * @param global_navigation $navigation 474 * @param navigation_node $modulenode 475 */ 476 public function extend_navigation(global_navigation $navigation, navigation_node $modulenode=null) { 477 $this->ensure_isset(array('context', 'component')); 478 479 $areas = $this->get_available_areas(); 480 foreach ($areas as $areaname => $areatitle) { 481 $this->set_area($areaname); 482 if ($controller = $this->get_active_controller()) { 483 $controller->extend_navigation($navigation, $modulenode); 484 } 485 } 486 } 487 488 /** 489 * Returns the given method's controller in the gradable area 490 * 491 * @param string $method the method name, eg 'rubric' (must be available) 492 * @return grading_controller 493 */ 494 public function get_controller($method) { 495 global $CFG, $DB; 496 497 $this->ensure_isset(array('context', 'component', 'area')); 498 499 // make sure the passed method is a valid plugin name 500 if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) { 501 throw new moodle_exception('invalid_method_name', 'core_grading'); 502 } 503 $available = $this->get_available_methods(false); 504 if (!array_key_exists($method, $available)) { 505 throw new moodle_exception('invalid_method_name', 'core_grading'); 506 } 507 508 // get the current grading area record if it exists 509 if (is_null($this->areacache)) { 510 $this->areacache = $DB->get_record('grading_areas', array( 511 'contextid' => $this->context->id, 512 'component' => $this->component, 513 'areaname' => $this->area), 514 '*', IGNORE_MISSING); 515 } 516 517 if ($this->areacache === false) { 518 // no area record yet, create one 519 $area = array( 520 'contextid' => $this->context->id, 521 'component' => $this->component, 522 'areaname' => $this->area); 523 $areaid = $DB->insert_record('grading_areas', $area); 524 // reload the cache 525 $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST); 526 } 527 528 require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php'); 529 $classname = 'gradingform_'.$method.'_controller'; 530 531 return new $classname($this->context, $this->component, $this->area, $this->areacache->id); 532 } 533 534 /** 535 * Returns the controller for the active method if it is available 536 * 537 * @return null|grading_controller 538 */ 539 public function get_active_controller() { 540 if ($gradingmethod = $this->get_active_method()) { 541 $controller = $this->get_controller($gradingmethod); 542 if ($controller->is_form_available()) { 543 return $controller; 544 } 545 } 546 return null; 547 } 548 549 /** 550 * Returns the URL of the grading area management page 551 * 552 * @param moodle_url $returnurl optional URL of the page where the user should be sent back to 553 * @return moodle_url 554 */ 555 public function get_management_url(moodle_url $returnurl = null) { 556 557 $this->ensure_isset(array('context', 'component')); 558 559 if ($this->areacache) { 560 $params = array('areaid' => $this->areacache->id); 561 } else { 562 $params = array('contextid' => $this->context->id, 'component' => $this->component); 563 if ($this->area) { 564 $params['area'] = $this->area; 565 } 566 } 567 568 if (!is_null($returnurl)) { 569 $params['returnurl'] = $returnurl->out(false); 570 } 571 572 return new moodle_url('/grade/grading/manage.php', $params); 573 } 574 575 /** 576 * Creates a new shared area to hold a grading form template 577 * 578 * Shared area are implemented as virtual gradable areas at the system level context 579 * with the component set to core_grading and unique random area name. 580 * 581 * @param string $method the name of the plugin we create the area for 582 * @return int the new area id 583 */ 584 public function create_shared_area($method) { 585 global $DB; 586 587 // generate some unique random name for the new area 588 $name = $method . '_' . sha1(rand().uniqid($method, true)); 589 // create new area record 590 $area = array( 591 'contextid' => context_system::instance()->id, 592 'component' => 'core_grading', 593 'areaname' => $name, 594 'activemethod' => $method); 595 return $DB->insert_record('grading_areas', $area); 596 } 597 598 /** 599 * Removes all data associated with the given context 600 * 601 * This is called by {@link context::delete_content()} 602 * 603 * @param int $contextid context id 604 */ 605 public static function delete_all_for_context($contextid) { 606 global $DB; 607 608 $areaids = $DB->get_fieldset_select('grading_areas', 'id', 'contextid = ?', array($contextid)); 609 $methods = array_keys(self::available_methods(false)); 610 611 foreach($areaids as $areaid) { 612 $manager = get_grading_manager($areaid); 613 foreach ($methods as $method) { 614 $controller = $manager->get_controller($method); 615 $controller->delete_definition(); 616 } 617 } 618 619 $DB->delete_records_list('grading_areas', 'id', $areaids); 620 } 621 622 /** 623 * Helper method to tokenize the given string 624 * 625 * Splits the given string into smaller strings. This is a helper method for 626 * full text searching in grading forms. If the given string is surrounded with 627 * double quotes, the resulting array consists of a single item containing the 628 * quoted content. 629 * 630 * Otherwise, string like 'grammar, english language' would be tokenized into 631 * the three tokens 'grammar', 'english', 'language'. 632 * 633 * One-letter tokens like are dropped in non-phrase mode. Repeated tokens are 634 * returned just once. 635 * 636 * @param string $needle 637 * @return array 638 */ 639 public static function tokenize($needle) { 640 641 // check if we are searching for the exact phrase 642 if (preg_match('/^[\s]*"[\s]*(.*?)[\s]*"[\s]*$/', $needle, $matches)) { 643 $token = $matches[1]; 644 if ($token === '') { 645 return array(); 646 } else { 647 return array($token); 648 } 649 } 650 651 // split the needle into smaller parts separated by non-word characters 652 $tokens = preg_split("/\W/u", $needle); 653 // keep just non-empty parts 654 $tokens = array_filter($tokens); 655 // distinct 656 $tokens = array_unique($tokens); 657 // drop one-letter tokens 658 foreach ($tokens as $ix => $token) { 659 if (strlen($token) == 1) { 660 unset($tokens[$ix]); 661 } 662 } 663 664 return array_values($tokens); 665 } 666 667 // ////////////////////////////////////////////////////////////////////////// 668 669 /** 670 * Make sure that the given properties were set to some not-null value 671 * 672 * @param array $properties the list of properties 673 * @throws coding_exception 674 */ 675 private function ensure_isset(array $properties) { 676 foreach ($properties as $property) { 677 if (!isset($this->$property)) { 678 throw new coding_exception('The property "'.$property.'" is not set.'); 679 } 680 } 681 } 682 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |