MDL-29479 Improved grading manager factory function
[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
DM
225
226 // make sure the passed method is a valid plugin name
227 if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) {
228 throw new moodle_exception('invalid_method_name', 'core_grading');
229 }
230 $available = $this->get_available_methods(false);
231 if (!array_key_exists($method, $available)) {
232 throw new moodle_exception('invalid_method_name', 'core_grading');
233 }
234
235 // get the current grading area record if it exists
9b8550f8
DM
236 if (is_null($this->areacache)) {
237 $this->areacache = $DB->get_record('grading_areas', array(
238 'contextid' => $this->context->id,
239 'component' => $this->component,
240 'areaname' => $this->area),
241 '*', IGNORE_MISSING);
242 }
64402867 243
9b8550f8 244 if ($this->areacache === false) {
64402867
DM
245 // no area record yet, create one with the active method set
246 $area = array(
247 'contextid' => $this->context->id,
248 'component' => $this->component,
249 'areaname' => $this->area,
250 'activemethod' => $method);
251 $DB->insert_record('grading_areas', $area);
252
253 } else {
254 // update the existing record if needed
9b8550f8
DM
255 if ($this->areacache->activemethod != $method) {
256 $DB->set_field('grading_areas', 'activemethod', $method, array('id' => $this->areacache->id));
64402867
DM
257 }
258 }
9b8550f8
DM
259
260 $this->areacache = null;
261 }
262
263 /**
264 * Extends the settings navigation with the grading settings
265 *
266 * This function is called when the context for the page is an activity module with the
267 * FEATURE_ADVANCED_GRADING and the user has the permission moodle/grade:managegradingforms.
268 *
269 * @param settings_navigation $settingsnav {@link settings_navigation}
270 * @param navigation_node $modulenode {@link navigation_node}
271 */
272 public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $modulenode=null) {
273 global $PAGE, $CFG;
274
275 $this->ensure_isset(array('context', 'component'));
276
277 $areas = $this->get_available_areas();
278
279 if (empty($areas)) {
280 // no money, no funny
281 return;
282 }
283
284 foreach ($areas as $areaname => $areatitle) {
285 $this->set_area($areaname);
286 $method = $this->get_active_method();
287
288 if (empty($method)) {
289 // no grading method selected for the given area - nothing to display
290 continue;
291 }
292
293 if (count($areas) > 1) {
294 // if the module supports multiple gradable areas, make a node for each of them
295 $node = $modulenode->add(get_string('gradinginarea', 'core_grading', $areatitle), null, settings_navigation::NODETYPE_BRANCH);
296 } else {
297 // otherwise put the items directly into the module's node
298 $node = $modulenode;
299 }
300
301 $controller = $this->get_controller($method);
302 $controller->extend_settings_navigation($settingsnav, $node);
303 }
304 }
305
306 /**
307 * Returns the given method's controller in the gradable area
308 *
309 * @param string $method the method name, eg 'rubric' (must be available)
310 * @return grading_controller
311 */
312 public function get_controller($method) {
313 global $CFG;
314
315 $this->ensure_isset(array('context', 'component', 'area'));
316
317 // make sure the passed method is a valid plugin name
318 if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) {
319 throw new moodle_exception('invalid_method_name', 'core_grading');
320 }
321 $available = $this->get_available_methods(false);
322 if (!array_key_exists($method, $available)) {
323 throw new moodle_exception('invalid_method_name', 'core_grading');
324 }
325
326 // get the current grading area record if it exists
327 if (is_null($this->areacache)) {
328 $this->areacache = $DB->get_record('grading_areas', array(
329 'contextid' => $this->context->id,
330 'component' => $this->component,
331 'areaname' => $this->area),
332 '*', IGNORE_MISSING);
333 }
334
335 if ($this->areacache === false) {
336 // no area record yet, create one
337 $area = array(
338 'contextid' => $this->context->id,
339 'component' => $this->component,
340 'areaname' => $this->area);
341 $areaid = $DB->insert_record('grading_areas', $area);
342 // reload the cache
343 $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST);
344 }
345
346 require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
347 $classname = $method.'_grading_controller';
348
349 return new $classname($this->context, $this->component, $this->area, $this->areacache->id);
4333580e
DM
350 }
351
9b8550f8 352 ////////////////////////////////////////////////////////////////////////////
64402867 353
4333580e
DM
354 /**
355 * Make sure that the given properties were set to some not-null value
356 *
357 * @param array $properties the list of properties
358 * @throws coding_exception
359 */
360 private function ensure_isset(array $properties) {
361 foreach ($properties as $property) {
362 if (!isset($this->$property)) {
9b8550f8 363 throw new coding_exception('The property "'.$property.'" is not set.');
4333580e
DM
364 }
365 }
366 }
367}