Saving a form as a public template
[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      * Returns the form definition suitable for cloning into another area
188      *
189      * @param gradingform_controller $target the controller of the new copy
190      * @return stdClass definition structure to pass to the target's {@link update_definition()}
191      */
192     public function get_definition_copy(gradingform_controller $target) {
194         if (get_class($this) != get_class($target)) {
195             throw new coding_exception('The source and copy controller mismatch');
196         }
198         if ($target->is_form_defined()) {
199             throw new coding_exception('The target controller already contains a form definition');
200         }
202         $old = $this->get_definition();
203         // keep our id
204         $new = new stdClass();
205         $new->copiedfromid = $old->id;
206         $new->name = $old->name;
207         // once we support files embedded into the description, we will want to
208         // relink them into the new file area here (that is why we accept $target)
209         $new->description = $old->description;
210         $new->descriptionformat = $old->descriptionformat;
211         $new->options = $old->options;
213         return $new;
214     }
216     /**
217      * Saves the defintion data into the database
218      *
219      * The implementation in this base class stores the common data into the record
220      * into the {grading_definition} table. The plugins are likely to extend this
221      * and save their data into own tables, too.
222      *
223      * @param stdClass $definition data containing values for the {grading_definition} table
224      * @param int|null $usermodified optional userid of the author of the definition, defaults to the current user
225      */
226     public function update_definition(stdClass $definition, $usermodified = null) {
227         global $DB, $USER;
229         if (is_null($usermodified)) {
230             $usermodified = $USER->id;
231         }
233         if (!empty($this->definition->id)) {
234             // prepare a record to be updated
235             $record = new stdClass();
236             // populate it with scalar values from the passed definition structure
237             foreach ($definition as $prop => $val) {
238                 if (is_array($val) or is_object($val)) {
239                     // probably plugin's data
240                     continue;
241                 }
242                 $record->{$prop} = $val;
243             }
244             // make sure we do not override some crucial values by accident
245             if (!empty($record->id) and $record->id != $this->definition->id) {
246                 throw new coding_exception('Attempting to update other definition record.');
247             }
248             $record->id = $this->definition->id;
249             unset($record->areaid);
250             unset($record->method);
251             unset($record->timecreated);
252             // set the modification flags
253             $record->timemodified = time();
254             $record->usermodified = $usermodified;
256             $DB->update_record('grading_definitions', $record);
258         } else if ($this->definition === false) {
259             // prepare a record to be inserted
260             $record = new stdClass();
261             // populate it with scalar values from the passed definition structure
262             foreach ($definition as $prop => $val) {
263                 if (is_array($val) or is_object($val)) {
264                     // probably plugin's data
265                     continue;
266                 }
267                 $record->{$prop} = $val;
268             }
269             // make sure we do not override some crucial values by accident
270             if (!empty($record->id)) {
271                 throw new coding_exception('Attempting to create a new record while there is already one existing.');
272             }
273             unset($record->id);
274             $record->areaid       = $this->areaid;
275             $record->method       = $this->get_method_name();
276             $record->timecreated  = time();
277             $record->usercreated  = $usermodified;
278             $record->timemodified = $record->timecreated;
279             $record->usermodified = $record->usercreated;
280             $record->status       = self::DEFINITION_STATUS_WORKINPROGRESS;
282             $DB->insert_record('grading_definitions', $record);
284         } else {
285             throw new coding_exception('Unknown status of the cached definition record.');
286         }
287     }
289     /**
290      * Makes sure there is a form instance for the given rater grading the given item
291      *
292      * Plugins will probably override/extend this and load additional data of how their
293      * forms are filled in one complex query.
294      *
295      * @todo this might actually become abstract method
296      * @param int $raterid
297      * @param int $itemid
298      * @return stdClass newly created or existing record from {grading_instances}
299      */
300     public function prepare_instance($raterid, $itemid) {
301         global $DB;
303         if (empty($this->definition)) {
304             throw new coding_exception('Attempting to prepare an instance of non-existing grading form');
305         }
307         $current = $DB->get_record('grading_instances', array(
308             'formid'  => $this->definition->id,
309             'raterid' => $raterid,
310             'itemid'  => $itemid), '*', IGNORE_MISSING);
312         if (empty($current)) {
313             $instance = new stdClass();
314             $instance->formid = $this->definition->id;
315             $instance->raterid = $raterid;
316             $instance->itemid = $itemid;
317             $instance->timemodified = time();
318             $instance->feedbackformat = FORMAT_MOODLE;
319             $instance->id = $DB->insert_record('grading_instances', $instance);
320             return $instance;
322         } else {
323             return $current;
324         }
325     }
327     /**
328      * Saves non-js data and returns the gradebook grade
329      */
330     abstract public function save_and_get_grade($raterid, $itemid, $formdata);
332     /**
333      * Returns html for form element
334      */
335     abstract public function to_html($gradingformelement);
337     /**
338      *
339      */
340     public function default_validation_error_message() {
341         return '';
342     }
344     /**
345      *
346      */
347     public function validate_grading_element($elementvalue, $itemid) {
348         return true;
349     }
351     /**
352      * Returns the HTML code displaying the preview of the grading form
353      *
354      * Plugins are supposed to override/extend this. Ideally they should delegate
355      * the task to their own renderer.
356      *
357      * @param moodle_page $page the target page
358      * @return string
359      */
360     public function render_preview(moodle_page $page) {
362         if (!$this->is_form_defined()) {
363             throw new coding_exception('It is the caller\'s responsibility to make sure that the form is actually defined');
364         }
366         $output = $page->get_renderer('core_grading');
368         return $output->preview_definition_header($this);
369     }
371     ////////////////////////////////////////////////////////////////////////////
374     /**
375      * Loads the form definition if it exists
376      *
377      * The default implementation just tries to load the record from the {grading_definitions}
378      * table. The plugins are likely to override this with a more complex query that loads
379      * all required data at once.
380      */
381     protected function load_definition() {
382         global $DB;
383         $this->definition = $DB->get_record('grading_definitions', array(
384             'areaid' => $this->areaid,
385             'method' => $this->get_method_name()), '*', IGNORE_MISSING);
386     }
388     /**
389      * @return string the name of the grading method plugin, eg 'rubric'
390      * @see PARAM_PLUGIN
391      */
392     protected function get_method_name() {
393         if (preg_match('/^gradingform_([a-z][a-z0-9_]*[a-z0-9])_controller$/', get_class($this), $matches)) {
394             return $matches[1];
395         } else {
396             throw new coding_exception('Invalid class name');
397         }
398     }