Trivial rename of the active method selector param
[moodle.git] / grade / grading / form / 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  * Common classes used by gradingform plugintypes are defined here
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  * Grading method controller represents a plugin used in a particular area
31  */
32 abstract class gradingform_controller {
34     const DEFINITION_STATUS_WORKINPROGRESS  = 0;
35     const DEFINITION_STATUS_PRIVATE         = 1;
36     const DEFINITION_STATUS_PUBLIC          = 2;
38     /** @var stdClass the context */
39     protected $context;
41     /** @var string the frankenstyle name of the component */
42     protected $component;
44     /** @var string the name of the gradable area */
45     protected $area;
47     /** @var int the id of the gradable area record */
48     protected $areaid;
50     /** @var stdClass|false the definition structure */
51     protected $definition;
53     /**
54      * Do not instantinate this directly, use {@link grading_manager::get_controller()}
55      *
56      * @param stdClass $context the context of the form
57      * @param string $component the frankenstyle name of the component
58      * @param string $area the name of the gradable area
59      * @param int $areaid the id of the gradable area record
60      */
61     public function __construct(stdClass $context, $component, $area, $areaid) {
62         global $DB;
64         $this->context      = $context;
65         list($type, $name)  = normalize_component($component);
66         $this->component    = $type.'_'.$name;
67         $this->area         = $area;
68         $this->areaid       = $areaid;
70         $this->load_definition();
71     }
73     /**
74      * @return stdClass controller context
75      */
76     public function get_context() {
77         return $this->context;
78     }
80     /**
81      * @return string gradable component name
82      */
83     public function get_component() {
84         return $this->component;
85     }
87     /**
88      * @return string gradable area name
89      */
90     public function get_area() {
91         return $this->area;
92     }
94     /**
95      * @return int gradable area id
96      */
97     public function get_areaid() {
98         return $this->areaid;
99     }
101     /**
102      * Is the form definition record available?
103      *
104      * Note that this actually checks whether the process of defining the form ever started
105      * and not whether the form definition should be considered as final.
106      *
107      * @return boolean
108      */
109     public function is_form_defined() {
110         return !empty($this->definition);
111     }
113     /**
114      * Is the grading form defined and released for usage by the given user?
115      *
116      * @param int $foruserid the id of the user who attempts to work with the form
117      * @return boolean
118      */
119     public function is_form_available($foruserid = null) {
120         global $USER;
122         if (is_null($foruserid)) {
123             $foruserid = $USER->id;
124         }
126         if (!$this->is_form_defined()) {
127             return false;
128         }
130         if ($this->definition->status == self::DEFINITION_STATUS_PUBLIC) {
131             return true;
132         }
134         if ($this->definition->status == self::DEFINITION_STATUS_PRIVATE) {
135             if ($this->definition->usercreated == $foruserid) {
136                 return true;
137             }
138         }
140         return false;
141     }
143     /**
144      * Returns URL of a page where the grading form can be defined and edited.
145      *
146      * @param moodle_url $returnurl optional URL of a page where the user should be sent once they are finished with editing
147      * @return moodle_url
148      */
149     public function get_editor_url(moodle_url $returnurl = null) {
151         $params = array('areaid' => $this->areaid);
153         if (!is_null($returnurl)) {
154             $params['returnurl'] = $returnurl->out(false);
155         }
157         return new moodle_url('/grade/grading/form/'.$this->get_method_name().'/edit.php', $params);
158     }
160     /**
161      * Extends the module settings navigation
162      *
163      * This function is called when the context for the page is an activity module with the
164      * FEATURE_ADVANCED_GRADING, the user has the permission moodle/grade:managegradingforms
165      * and there is an area with the active grading method set to the given plugin.
166      *
167      * @param settings_navigation $settingsnav {@link settings_navigation}
168      * @param navigation_node $node {@link navigation_node}
169      */
170     public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $node=null) {
171         // do not extend by default
172     }
174     /**
175      * Returns the grading form definition structure
176      *
177      * @return stdClass|false definition data or false if the form is not defined yet
178      */
179     public function get_definition() {
180         if (is_null($this->definition)) {
181             $this->load_definition();
182         }
183         return $this->definition;
184     }
186     /**
187      * Saves the defintion data into the database
188      *
189      * The implementation in this base class stores the common data into the record
190      * into the {grading_definition} table. The plugins are likely to extend this
191      * and save their data into own tables, too.
192      *
193      * @param stdClass $definition data containing values for the {grading_definition} table
194      * @param int|null $usermodified optional userid of the author of the definition, defaults to the current user
195      */
196     public function update_definition(stdClass $definition, $usermodified = null) {
197         global $DB, $USER;
199         if (is_null($usermodified)) {
200             $usermodified = $USER->id;
201         }
203         if (!empty($this->definition->id)) {
204             // prepare a record to be updated
205             $record = new stdClass();
206             // populate it with scalar values from the passed definition structure
207             foreach ($definition as $prop => $val) {
208                 if (is_array($val) or is_object($val)) {
209                     // probably plugin's data
210                     continue;
211                 }
212                 $record->{$prop} = $val;
213             }
214             // make sure we do not override some crucial values by accident
215             if (!empty($record->id) and $record->id != $this->definition->id) {
216                 throw new coding_exception('Attempting to update other definition record.');
217             }
218             $record->id = $this->definition->id;
219             unset($record->areaid);
220             unset($record->method);
221             unset($record->timecreated);
222             // set the modification flags
223             $record->timemodified = time();
224             $record->usermodified = $usermodified;
226             $DB->update_record('grading_definitions', $record);
228         } else if ($this->definition === false) {
229             // prepare a record to be inserted
230             $record = new stdClass();
231             // populate it with scalar values from the passed definition structure
232             foreach ($definition as $prop => $val) {
233                 if (is_array($val) or is_object($val)) {
234                     // probably plugin's data
235                     continue;
236                 }
237                 $record->{$prop} = $val;
238             }
239             // make sure we do not override some crucial values by accident
240             if (!empty($record->id)) {
241                 throw new coding_exception('Attempting to create a new record while there is already one existing.');
242             }
243             unset($record->id);
244             $record->areaid       = $this->areaid;
245             $record->method       = $this->get_method_name();
246             $record->timecreated  = time();
247             $record->usercreated  = $usermodified;
248             $record->timemodified = $record->timecreated;
249             $record->usermodified = $record->usercreated;
250             $record->status       = self::DEFINITION_STATUS_WORKINPROGRESS;
252             $DB->insert_record('grading_definitions', $record);
254         } else {
255             throw new coding_exception('Unknown status of the cached definition record.');
256         }
257     }
259     /**
260      * Makes sure there is a form instance for the given rater grading the given item
261      *
262      * Plugins will probably override/extend this and load additional data of how their
263      * forms are filled in one complex query.
264      *
265      * @todo this might actually become abstract method
266      * @param int $raterid
267      * @param int $itemid
268      * @return stdClass newly created or existing record from {grading_instances}
269      */
270     public function prepare_instance($raterid, $itemid) {
271         global $DB;
273         if (empty($this->definition)) {
274             throw new coding_exception('Attempting to prepare an instance of non-existing grading form');
275         }
277         $current = $DB->get_record('grading_instances', array(
278             'formid'  => $this->definition->id,
279             'raterid' => $raterid,
280             'itemid'  => $itemid), '*', IGNORE_MISSING);
282         if (empty($current)) {
283             $instance = new stdClass();
284             $instance->formid = $this->definition->id;
285             $instance->raterid = $raterid;
286             $instance->itemid = $itemid;
287             $instance->timemodified = time();
288             $instance->feedbackformat = FORMAT_MOODLE;
289             $instance->id = $DB->insert_record('grading_instances', $instance);
290             return $instance;
292         } else {
293             return $current;
294         }
295     }
297     /**
298      * Saves non-js data and returns the gradebook grade
299      */
300     abstract public function save_and_get_grade($raterid, $itemid, $formdata);
302     /**
303      * Returns html for form element
304      */
305     abstract public function to_html($gradingformelement);
307     /**
308      *
309      */
310     public function default_validation_error_message() {
311         return '';
312     }
314     /**
315      *
316      */
317     public function validate_grading_element($elementvalue, $itemid) {
318         return true;
319     }
321     /**
322      * Returns the HTML code displaying the preview of the grading form
323      *
324      * Plugins are supposed to override/extend this. Ideally they should delegate
325      * the task to their own renderer.
326      *
327      * @param moodle_page $page the target page
328      * @return string
329      */
330     public function render_preview(moodle_page $page) {
332         if (!$this->is_form_defined()) {
333             throw new coding_exception('It is the caller\'s responsibility to make sure that the form is actually defined');
334         }
336         $output = $page->get_renderer('core_grading');
338         return $output->preview_definition_header($this);
339     }
341     ////////////////////////////////////////////////////////////////////////////
344     /**
345      * Loads the form definition if it exists
346      *
347      * The default implementation just tries to load the record from the {grading_definitions}
348      * table. The plugins are likely to override this with a more complex query that loads
349      * all required data at once.
350      */
351     protected function load_definition() {
352         global $DB;
353         $this->definition = $DB->get_record('grading_definitions', array(
354             'areaid' => $this->areaid,
355             'method' => $this->get_method_name()), '*', IGNORE_MISSING);
356     }
358     /**
359      * @return string the name of the grading method plugin, eg 'rubric'
360      * @see PARAM_PLUGIN
361      */
362     protected function get_method_name() {
363         if (preg_match('/^gradingform_([a-z][a-z0-9_]*[a-z0-9])_controller$/', get_class($this), $matches)) {
364             return $matches[1];
365         } else {
366             throw new coding_exception('Invalid class name');
367         }
368     }