MDL-29480 Adding new fields into grading_definitions
[moodle.git] / grade / grading / lib.php
CommitLineData
4333580e
DM
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * Advanced grading methods support
20 *
21 * @package core
22 * @subpackage grading
23 * @copyright 2011 David Mudrak <david@moodle.com>
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27defined('MOODLE_INTERNAL') || die();
28
29/**
30 * Factory method returning an instance of the grading manager
31 *
fb13a148 32 * @param stdClass|int $context or $areaid
4333580e 33 * @param string $component the frankenstyle name of the component
8a4acb3a 34 * @param string $area the name of the gradable area
4333580e
DM
35 * @return grading_manager
36 */
fb13a148
DM
37function get_grading_manager($context_or_areaid = null, $component = null, $area = null) {
38 global $DB;
4333580e
DM
39
40 $manager = new grading_manager();
41
fb13a148
DM
42 if (is_object($context_or_areaid)) {
43 $context = $context_or_areaid;
44 } else {
45 $context = null;
46
47 if (is_numeric($context_or_areaid)) {
48 $manager->load($context_or_areaid);
49 return $manager;
50 }
51 }
52
4333580e
DM
53 if (!is_null($context)) {
54 $manager->set_context($context);
55 }
56
57 if (!is_null($component)) {
58 $manager->set_component($component);
59 }
60
8a4acb3a
DM
61 if (!is_null($area)) {
62 $manager->set_area($area);
4333580e
DM
63 }
64
65 return $manager;
66}
67
68/**
69 * General class providing access to common grading features
70 *
9b8550f8
DM
71 * Grading manager provides access to the particular grading method controller
72 * in that area.
73 *
4333580e
DM
74 * Fully initialized instance of the grading manager operates over a single
75 * gradable area. It is possible to work with a partially initialized manager
8a4acb3a
DM
76 * that knows just context and component without known area, for example.
77 * It is also possible to change context, component and area of an existing
4333580e
DM
78 * manager. Such pattern is used when copying form definitions, for example.
79 */
80class grading_manager {
81
82 /** @var stdClass the context */
83 protected $context;
84
85 /** @var string the frankenstyle name of the component */
86 protected $component;
87
88 /** @var string the name of the gradable area */
8a4acb3a 89 protected $area;
4333580e 90
9b8550f8
DM
91 /** @var stdClass|false|null the raw record from {grading_areas}, false if does not exist, null if invalidated cache */
92 private $areacache = null;
93
4333580e
DM
94 /**
95 * Sets the context the manager operates on
96 *
97 * @param stdClass $context
98 */
99 public function set_context(stdClass $context) {
9b8550f8 100 $this->areacache = null;
4333580e
DM
101 $this->context = $context;
102 }
103
104 /**
105 * Sets the component the manager operates on
106 *
107 * @param string $component the frankenstyle name of the component
108 */
109 public function set_component($component) {
9b8550f8
DM
110 $this->areacache = null;
111 list($type, $name) = normalize_component($component);
112 $this->component = $type.'_'.$name;
4333580e
DM
113 }
114
115 /**
8a4acb3a 116 * Sets the area the manager operates on
4333580e 117 *
8a4acb3a 118 * @param string $area the name of the gradable area
4333580e 119 */
8a4acb3a 120 public function set_area($area) {
9b8550f8 121 $this->areacache = null;
8a4acb3a 122 $this->area = $area;
4333580e
DM
123 }
124
fb13a148
DM
125 /**
126 * Loads the gradable area info from the database
127 *
128 * @param int $areaid
129 */
130 public function load($areaid) {
131 global $DB;
132
133 $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST);
134 $this->context = get_context_instance_by_id($this->areacache->contextid, MUST_EXIST);
135 $this->component = $this->areacache->component;
136 $this->area = $this->areacache->areaname;
137 }
138
4333580e
DM
139 /**
140 * Returns the list of available grading methods in the given context
141 *
142 * Basically this returns the list of installed grading plugins with an empty value
143 * for simple direct grading. In the future, the list of available methods may be
144 * controlled per-context.
145 *
146 * Requires the context property to be set in advance.
6c9e506c
DM
147 *
148 * @param bool $includenone should the 'Simple direct grading' be included
4333580e
DM
149 * @return array of the (string)name => (string)localized title of the method
150 */
6c9e506c 151 public function get_available_methods($includenone = true) {
4333580e
DM
152
153 $this->ensure_isset(array('context'));
154
6c9e506c
DM
155 if ($includenone) {
156 $list = array('' => get_string('gradingmethodnone', 'core_grading'));
157 } else {
158 $list = array();
159 }
160
161 foreach (get_plugin_list('gradingform') as $name => $location) {
162 $list[$name] = get_string('pluginname', 'gradingform_'.$name);
163 }
164
165 return $list;
4333580e
DM
166 }
167
168 /**
169 * Returns the list of gradable areas in the given context and component
170 *
171 * This performs a callback to the library of the relevant plugin to obtain
172 * the list of supported areas.
173 * @return array of (string)areacode => (string)localized title of the area
174 */
175 public function get_available_areas() {
176 global $CFG;
177
178 $this->ensure_isset(array('context', 'component'));
179
180 // example: if the given context+component lead to mod_assignment, this method
181 // will do something like
182 // require_once($CFG->dirroot.'/mod/assignment/lib.php');
183 // return assignment_gradable_area_list();
184
185 // todo - hardcoded list for now
9b8550f8 186 return array('submission' => 'Submissions');
4333580e
DM
187 }
188
189 /**
8a4acb3a 190 * Returns the currently active grading method in the gradable area
4333580e 191 *
64402867 192 * @return string|null the name of the grading plugin of null if it has not been set
4333580e 193 */
8a4acb3a 194 public function get_active_method() {
64402867
DM
195 global $DB;
196
197 $this->ensure_isset(array('context', 'component', 'area'));
198
199 // get the current grading area record if it exists
9b8550f8
DM
200 if (is_null($this->areacache)) {
201 $this->areacache = $DB->get_record('grading_areas', array(
202 'contextid' => $this->context->id,
203 'component' => $this->component,
204 'areaname' => $this->area),
205 '*', IGNORE_MISSING);
206 }
64402867 207
9b8550f8 208 if ($this->areacache === false) {
64402867
DM
209 // no area record yet
210 return null;
211 }
212
9b8550f8 213 return $this->areacache->activemethod;
64402867
DM
214 }
215
216 /**
217 * Sets the currently active grading method in the gradable area
218 *
219 * @param string $method the method name, eg 'rubric' (must be available)
220 */
221 public function set_active_method($method) {
222 global $DB;
223
8a4acb3a 224 $this->ensure_isset(array('context', 'component', 'area'));
64402867 225
8cd65f16
DM
226 // make sure the passed method is empty or a valid plugin name
227 if (empty($method)) {
228 $method = null;
229 } else {
230 if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) {
231 throw new moodle_exception('invalid_method_name', 'core_grading');
232 }
233 $available = $this->get_available_methods(false);
234 if (!array_key_exists($method, $available)) {
235 throw new moodle_exception('invalid_method_name', 'core_grading');
236 }
64402867
DM
237 }
238
239 // get the current grading area record if it exists
9b8550f8
DM
240 if (is_null($this->areacache)) {
241 $this->areacache = $DB->get_record('grading_areas', array(
242 'contextid' => $this->context->id,
243 'component' => $this->component,
244 'areaname' => $this->area),
245 '*', IGNORE_MISSING);
246 }
64402867 247
9b8550f8 248 if ($this->areacache === false) {
64402867
DM
249 // no area record yet, create one with the active method set
250 $area = array(
251 'contextid' => $this->context->id,
252 'component' => $this->component,
253 'areaname' => $this->area,
254 'activemethod' => $method);
255 $DB->insert_record('grading_areas', $area);
256
257 } else {
258 // update the existing record if needed
8cd65f16 259 if ($this->areacache->activemethod !== $method) {
9b8550f8 260 $DB->set_field('grading_areas', 'activemethod', $method, array('id' => $this->areacache->id));
64402867
DM
261 }
262 }
9b8550f8
DM
263
264 $this->areacache = null;
265 }
266
267 /**
268 * Extends the settings navigation with the grading settings
269 *
270 * This function is called when the context for the page is an activity module with the
271 * FEATURE_ADVANCED_GRADING and the user has the permission moodle/grade:managegradingforms.
272 *
273 * @param settings_navigation $settingsnav {@link settings_navigation}
274 * @param navigation_node $modulenode {@link navigation_node}
275 */
276 public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $modulenode=null) {
277 global $PAGE, $CFG;
278
279 $this->ensure_isset(array('context', 'component'));
280
281 $areas = $this->get_available_areas();
282
283 if (empty($areas)) {
284 // no money, no funny
285 return;
286 }
287
288 foreach ($areas as $areaname => $areatitle) {
289 $this->set_area($areaname);
290 $method = $this->get_active_method();
291
292 if (empty($method)) {
293 // no grading method selected for the given area - nothing to display
294 continue;
295 }
296
297 if (count($areas) > 1) {
298 // if the module supports multiple gradable areas, make a node for each of them
299 $node = $modulenode->add(get_string('gradinginarea', 'core_grading', $areatitle), null, settings_navigation::NODETYPE_BRANCH);
300 } else {
301 // otherwise put the items directly into the module's node
302 $node = $modulenode;
303 }
304
305 $controller = $this->get_controller($method);
306 $controller->extend_settings_navigation($settingsnav, $node);
307 }
308 }
309
310 /**
311 * Returns the given method's controller in the gradable area
312 *
313 * @param string $method the method name, eg 'rubric' (must be available)
314 * @return grading_controller
315 */
316 public function get_controller($method) {
317 global $CFG;
318
319 $this->ensure_isset(array('context', 'component', 'area'));
320
321 // make sure the passed method is a valid plugin name
322 if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) {
323 throw new moodle_exception('invalid_method_name', 'core_grading');
324 }
325 $available = $this->get_available_methods(false);
326 if (!array_key_exists($method, $available)) {
327 throw new moodle_exception('invalid_method_name', 'core_grading');
328 }
329
330 // get the current grading area record if it exists
331 if (is_null($this->areacache)) {
332 $this->areacache = $DB->get_record('grading_areas', array(
333 'contextid' => $this->context->id,
334 'component' => $this->component,
335 'areaname' => $this->area),
336 '*', IGNORE_MISSING);
337 }
338
339 if ($this->areacache === false) {
340 // no area record yet, create one
341 $area = array(
342 'contextid' => $this->context->id,
343 'component' => $this->component,
344 'areaname' => $this->area);
345 $areaid = $DB->insert_record('grading_areas', $area);
346 // reload the cache
347 $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST);
348 }
349
350 require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
3e43eff5 351 $classname = 'gradingform_'.$method.'_controller';
9b8550f8
DM
352
353 return new $classname($this->context, $this->component, $this->area, $this->areacache->id);
4333580e
DM
354 }
355
9b8550f8 356 ////////////////////////////////////////////////////////////////////////////
64402867 357
4333580e
DM
358 /**
359 * Make sure that the given properties were set to some not-null value
360 *
361 * @param array $properties the list of properties
362 * @throws coding_exception
363 */
364 private function ensure_isset(array $properties) {
365 foreach ($properties as $property) {
366 if (!isset($this->$property)) {
9b8550f8 367 throw new coding_exception('The property "'.$property.'" is not set.');
4333580e
DM
368 }
369 }
370 }
371}