Trivial rename of the active method selector param
[moodle.git] / grade / grading / lib.php
1 <?php
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/>.
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  */
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Factory method returning an instance of the grading manager
31  *
32  * @param stdClass|int $context or $areaid
33  * @param string $component the frankenstyle name of the component
34  * @param string $area the name of the gradable area
35  * @return grading_manager
36  */
37 function get_grading_manager($context_or_areaid = null, $component = null, $area = null) {
38     global $DB;
40     $manager = new grading_manager();
42     if (is_object($context_or_areaid)) {
43         $context = $context_or_areaid;
44     } else {
45         $context = null;
47         if (is_numeric($context_or_areaid)) {
48             $manager->load($context_or_areaid);
49             return $manager;
50         }
51     }
53     if (!is_null($context)) {
54         $manager->set_context($context);
55     }
57     if (!is_null($component)) {
58         $manager->set_component($component);
59     }
61     if (!is_null($area)) {
62         $manager->set_area($area);
63     }
65     return $manager;
66 }
68 /**
69  * General class providing access to common grading features
70  *
71  * Grading manager provides access to the particular grading method controller
72  * in that area.
73  *
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
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
78  * manager. Such pattern is used when copying form definitions, for example.
79  */
80 class grading_manager {
82     /** @var stdClass the context */
83     protected $context;
85     /** @var string the frankenstyle name of the component */
86     protected $component;
88     /** @var string the name of the gradable area */
89     protected $area;
91     /** @var stdClass|false|null the raw record from {grading_areas}, false if does not exist, null if invalidated cache */
92     private $areacache = null;
94     /**
95      * @return stdClass grading manager context
96      */
97     public function get_context() {
98         return $this->context;
99     }
101     /**
102      * Sets the context the manager operates on
103      *
104      * @param stdClass $context
105      */
106     public function set_context(stdClass $context) {
107         $this->areacache = null;
108         $this->context = $context;
109     }
111     /**
112      * @return string grading manager component
113      */
114     public function get_component() {
115         return $this->component;
116     }
118     /**
119      * Sets the component the manager operates on
120      *
121      * @param string $component the frankenstyle name of the component
122      */
123     public function set_component($component) {
124         $this->areacache = null;
125         list($type, $name) = normalize_component($component);
126         $this->component = $type.'_'.$name;
127     }
129     /**
130      * @return string grading manager area name
131      */
132     public function get_area() {
133         return $this->area;
134     }
136     /**
137      * Sets the area the manager operates on
138      *
139      * @param string $area the name of the gradable area
140      */
141     public function set_area($area) {
142         $this->areacache = null;
143         $this->area = $area;
144     }
146     /**
147      * Returns a text describing the context and the component
148      *
149      * At the moment this works for gradable areas in course modules. In the future, this
150      * method should be improved so it works for other contexts (blocks, gradebook items etc)
151      * or subplugins.
152      *
153      * @return string
154      */
155     public function get_component_title() {
157         $this->ensure_isset(array('context', 'component'));
158         list($context, $course, $cm) = get_context_info_array($this->get_context()->id);
160         if (!empty($cm->name)) {
161             $title = $cm->name;
162         } else {
163             debugging('Gradable areas are currently supported at the course module level only', DEBUG_DEVELOPER);
164             $title = $this->get_component();
165         }
167         return $title;
168     }
170     /**
171      * Returns the localized title of the currently set area
172      *
173      * @return string
174      */
175     public function get_area_title() {
177         $this->ensure_isset(array('context', 'component', 'area'));
178         $areas = $this->get_available_areas();
180         return $areas[$this->get_area()];
181     }
183     /**
184      * Loads the gradable area info from the database
185      *
186      * @param int $areaid
187      */
188     public function load($areaid) {
189         global $DB;
191         $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST);
192         $this->context = get_context_instance_by_id($this->areacache->contextid, MUST_EXIST);
193         $this->component = $this->areacache->component;
194         $this->area = $this->areacache->areaname;
195     }
197     /**
198      * Returns the list of available grading methods in the given context
199      *
200      * Basically this returns the list of installed grading plugins with an empty value
201      * for simple direct grading. In the future, the list of available methods may be
202      * controlled per-context.
203      *
204      * Requires the context property to be set in advance.
205      *
206      * @param bool $includenone should the 'Simple direct grading' be included
207      * @return array of the (string)name => (string)localized title of the method
208      */
209     public function get_available_methods($includenone = true) {
211         $this->ensure_isset(array('context'));
213         if ($includenone) {
214             $list = array('' => get_string('gradingmethodnone', 'core_grading'));
215         } else {
216             $list = array();
217         }
219         foreach (get_plugin_list('gradingform') as $name => $location) {
220             $list[$name] = get_string('pluginname', 'gradingform_'.$name);
221         }
223         return $list;
224     }
226     /**
227      * Returns the list of gradable areas in the given context and component
228      *
229      * This performs a callback to the library of the relevant plugin to obtain
230      * the list of supported areas.
231      * @return array of (string)areacode => (string)localized title of the area
232      */
233     public function get_available_areas() {
234         global $CFG;
236         $this->ensure_isset(array('context', 'component'));
238         // example: if the given context+component lead to mod_assignment, this method
239         // will do something like
240         // require_once($CFG->dirroot.'/mod/assignment/lib.php');
241         // return assignment_gradable_area_list();
243         // todo - hardcoded list for now
244         return array('submission' => 'Submissions');
245     }
247     /**
248      * Returns the currently active grading method in the gradable area
249      *
250      * @return string|null the name of the grading plugin of null if it has not been set
251      */
252     public function get_active_method() {
253         global $DB;
255         $this->ensure_isset(array('context', 'component', 'area'));
257         // get the current grading area record if it exists
258         if (is_null($this->areacache)) {
259             $this->areacache = $DB->get_record('grading_areas', array(
260                 'contextid' => $this->context->id,
261                 'component' => $this->component,
262                 'areaname'  => $this->area),
263             '*', IGNORE_MISSING);
264         }
266         if ($this->areacache === false) {
267             // no area record yet
268             return null;
269         }
271         return $this->areacache->activemethod;
272     }
274     /**
275      * Sets the currently active grading method in the gradable area
276      *
277      * @param string $method the method name, eg 'rubric' (must be available)
278      * @return bool true if the method changed or was just set, false otherwise
279      */
280     public function set_active_method($method) {
281         global $DB;
283         $this->ensure_isset(array('context', 'component', 'area'));
285         // make sure the passed method is empty or a valid plugin name
286         if (empty($method)) {
287             $method = null;
288         } else {
289             if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) {
290                 throw new moodle_exception('invalid_method_name', 'core_grading');
291             }
292             $available = $this->get_available_methods(false);
293             if (!array_key_exists($method, $available)) {
294                 throw new moodle_exception('invalid_method_name', 'core_grading');
295             }
296         }
298         // get the current grading area record if it exists
299         if (is_null($this->areacache)) {
300             $this->areacache = $DB->get_record('grading_areas', array(
301                 'contextid' => $this->context->id,
302                 'component' => $this->component,
303                 'areaname'  => $this->area),
304             '*', IGNORE_MISSING);
305         }
307         $methodchanged = false;
309         if ($this->areacache === false) {
310             // no area record yet, create one with the active method set
311             $area = array(
312                 'contextid'     => $this->context->id,
313                 'component'     => $this->component,
314                 'areaname'      => $this->area,
315                 'activemethod'  => $method);
316             $DB->insert_record('grading_areas', $area);
317             $methodchanged = true;
319         } else {
320             // update the existing record if needed
321             if ($this->areacache->activemethod !== $method) {
322                 $DB->set_field('grading_areas', 'activemethod', $method, array('id' => $this->areacache->id));
323                 $methodchanged = true;
324             }
325         }
327         $this->areacache = null;
329         return $methodchanged;
330     }
332     /**
333      * Extends the settings navigation with the grading settings
334      *
335      * This function is called when the context for the page is an activity module with the
336      * FEATURE_ADVANCED_GRADING and the user has the permission moodle/grade:managegradingforms.
337      *
338      * @param settings_navigation $settingsnav {@link settings_navigation}
339      * @param navigation_node $modulenode {@link navigation_node}
340      */
341     public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $modulenode=null) {
343         $this->ensure_isset(array('context', 'component'));
345         $areas = $this->get_available_areas();
347         if (empty($areas)) {
348             // no money, no funny
349             return;
351         } else if (count($areas) == 1) {
352             // make just a single node for the management screen
353             $areatitle = reset($areas);
354             $areaname  = key($areas);
355             $this->set_area($areaname);
356             $method = $this->get_active_method();
357             $managementnode = $modulenode->add(get_string('gradingmanagement', 'core_grading'),
358                 $this->get_management_url(), settings_navigation::TYPE_CUSTOM);
359             if ($method) {
360                 $controller = $this->get_controller($method);
361                 $controller->extend_settings_navigation($settingsnav, $managementnode);
362             }
364         } else {
365             // make management screen node for each area
366             $managementnode = $modulenode->add(get_string('gradingmanagement', 'core_grading'),
367                 null, settings_navigation::TYPE_CUSTOM);
368             foreach ($areas as $areaname => $areatitle) {
369                 $this->set_area($areaname);
370                 $method = $this->get_active_method();
371                 $node = $managementnode->add($areatitle,
372                     $this->get_management_url(), settings_navigation::TYPE_CUSTOM);
373                 if ($method) {
374                     $controller = $this->get_controller($method);
375                     $controller->extend_settings_navigation($settingsnav, $node);
376                 }
377             }
378         }
379     }
381     /**
382      * Returns the given method's controller in the gradable area
383      *
384      * @param string $method the method name, eg 'rubric' (must be available)
385      * @return grading_controller
386      */
387     public function get_controller($method) {
388         global $CFG;
390         $this->ensure_isset(array('context', 'component', 'area'));
392         // make sure the passed method is a valid plugin name
393         if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) {
394             throw new moodle_exception('invalid_method_name', 'core_grading');
395         }
396         $available = $this->get_available_methods(false);
397         if (!array_key_exists($method, $available)) {
398             throw new moodle_exception('invalid_method_name', 'core_grading');
399         }
401         // get the current grading area record if it exists
402         if (is_null($this->areacache)) {
403             $this->areacache = $DB->get_record('grading_areas', array(
404                 'contextid' => $this->context->id,
405                 'component' => $this->component,
406                 'areaname'  => $this->area),
407             '*', IGNORE_MISSING);
408         }
410         if ($this->areacache === false) {
411             // no area record yet, create one
412             $area = array(
413                 'contextid' => $this->context->id,
414                 'component' => $this->component,
415                 'areaname'  => $this->area);
416             $areaid = $DB->insert_record('grading_areas', $area);
417             // reload the cache
418             $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST);
419         }
421         require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
422         $classname = 'gradingform_'.$method.'_controller';
424         return new $classname($this->context, $this->component, $this->area, $this->areacache->id);
425     }
427     /**
428      * Returns the controller for the active method if it is available
429      *
430      * @return null|grading_controller
431      */
432     public function get_active_controller() {
433         if ($gradingmethod = $this->get_active_method()) {
434             $controller = $this->get_controller($gradingmethod);
435             if ($controller->is_form_available()) {
436                 return $controller;
437             }
438         }
439         return null;
440     }
442     /**
443      * Returns the URL of the grading area management page
444      *
445      * @param moodle_url $returnurl optional URL of the page where the user should be sent back to
446      * @return moodle_url
447      */
448     public function get_management_url(moodle_url $returnurl = null) {
450         $this->ensure_isset(array('context', 'component'));
452         if ($this->areacache) {
453             $params = array('areaid' => $this->areacache->id);
454         } else {
455             $params = array('contextid' => $this->context->id, 'component' => $this->component);
456             if ($this->area) {
457                 $params['area'] = $this->area;
458             }
459         }
461         if (!is_null($returnurl)) {
462             $params['returnurl'] = $returnurl->out(false);
463         }
465         return new moodle_url('/grade/grading/manage.php', $params);
466     }
468     ////////////////////////////////////////////////////////////////////////////
470     /**
471      * Make sure that the given properties were set to some not-null value
472      *
473      * @param array $properties the list of properties
474      * @throws coding_exception
475      */
476     private function ensure_isset(array $properties) {
477         foreach ($properties as $property) {
478             if (!isset($this->$property)) {
479                 throw new coding_exception('The property "'.$property.'" is not set.');
480             }
481         }
482     }