Merged Marina's recent work on rubric plugin
[moodle.git] / grade / grading / form / lib.php
CommitLineData
9b8550f8
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/**
21d37aa6
DM
19 * Common classes used by gradingform plugintypes are defined here
20 *
9b8550f8
DM
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/**
21d37aa6 30 * Grading method controller represents a plugin used in a particular area
9b8550f8 31 */
3e43eff5 32abstract class gradingform_controller {
9b8550f8 33
21d37aa6
DM
34 const DEFINITION_STATUS_WORKINPROGRESS = 0;
35 const DEFINITION_STATUS_PRIVATE = 1;
36 const DEFINITION_STATUS_PUBLIC = 2;
37
9b8550f8
DM
38 /** @var stdClass the context */
39 protected $context;
40
41 /** @var string the frankenstyle name of the component */
42 protected $component;
43
44 /** @var string the name of the gradable area */
45 protected $area;
46
47 /** @var int the id of the gradable area record */
48 protected $areaid;
49
3e43eff5
DM
50 /** @var moodle_page the target page we embed our widgets to */
51 protected $page;
52
53 /** @var stdClass|false the raw {grading_definitions} record */
54 protected $definition;
55
56 /** @var bool is the target grading page finalized for sending output to the browser */
57 protected $pagefinalized = false;
58
21d37aa6
DM
59 /** @var array list of widgets made by this controller for $this->page */
60 protected $widgets = array();
61
9b8550f8
DM
62 /**
63 * Do not instantinate this directly, use {@link grading_manager::get_controller()}
64 *
3e43eff5 65 * @return gradingform_controller instance
9b8550f8
DM
66 */
67 public function __construct(stdClass $context, $component, $area, $areaid) {
3e43eff5
DM
68 global $DB;
69
70 $this->context = $context;
71 $this->component = $component;
72 $this->area = $area;
73 $this->areaid = $areaid;
74
75 $this->load_definition();
76 }
77
78 /**
21d37aa6 79 * Is the grading form defined and released for usage by the given user?
3e43eff5 80 *
21d37aa6 81 * @param int $foruserid the id of the user who attempts to work with the form
3e43eff5
DM
82 * @return boolean
83 */
21d37aa6
DM
84 public function is_form_available($foruserid = null) {
85 global $USER;
86
87 if (is_null($foruserid)) {
88 $foruserid = $USER->id;
89 }
90
91 if (empty($this->definition)) {
92 return false;
93 }
94
95 if ($this->definition->status == self::DEFINITION_STATUS_PUBLIC) {
96 return true;
97 }
98
99 if ($this->definition->status == self::DEFINITION_STATUS_PRIVATE) {
100 if ($this->definition->usercreated == $foruserid) {
101 return true;
102 }
103 }
104
105 return false;
3e43eff5
DM
106 }
107
108 /**
109 * Prepare a grading widget for the given rater and item
110 *
21d37aa6
DM
111 * The options array MUST contain (string)displayas set to either 'scale' or 'grade'.
112 * If scale is used, the (int)scaleid must be provided. If grade is used, (int)maxgrade
113 * must be provided and (int)decimals can be provided (defaults to 0).
114 * The options array CAN contain (bool)bulk to signalize whether there are more widgets to be
115 * made by this controller instance or whether this is the last one.
116 * If you make multiple widgets, pass bulk option se to true. Note that then it is
117 * the caller's responsibility to call {@link finalize_page()} method explicitly then.
3e43eff5
DM
118 *
119 * @param int $raterid the user who will use the widget for grading
120 * @param int $itemid the graded item
21d37aa6 121 * @param array $options the widget options
3e43eff5
DM
122 * @return gradingform_widget renderable widget to insert into the page
123 */
21d37aa6 124 abstract public function make_grading_widget($raterid, $itemid, array $options);
3e43eff5
DM
125
126 /**
127 * Does everything needed before the page is sent to the browser
128 */
129 public function finalize_page() {
130 $this->pagefinalized = true;
9b8550f8
DM
131 }
132
133 /**
134 * Extends the module settings navigation
135 *
136 * This function is called when the context for the page is an activity module with the
137 * FEATURE_ADVANCED_GRADING, the user has the permission moodle/grade:managegradingforms
138 * and there is an area with the active grading method set to the given plugin.
139 *
140 * @param settings_navigation $settingsnav {@link settings_navigation}
141 * @param navigation_node $node {@link navigation_node}
142 */
143 public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $node=null) {
144 // do not extend by default
145 }
3e43eff5
DM
146
147 /**
148 * Returns the name of the grading method, eg 'rubric'
149 */
150 abstract protected function get_method_name();
151
152 /**
153 * Sets the target page and returns a renderer for this plugin
154 *
155 * @param moodle_page $page the target page
156 * @return core_renderer
157 */
158 public function prepare_renderer(moodle_page $page) {
159 global $CFG;
160
161 $this->page = $page;
162 require_once($CFG->dirroot.'/grade/grading/form/'.$this->get_method_name().'/renderer.php');
163 return $page->get_renderer('gradingform_'.$this->get_method_name());
164 }
165
166 /**
21d37aa6
DM
167 * Saves the defintion data into database
168 *
169 * The default implementation stored the record into the {grading_definition} table. The
170 * plugins are likely to extend this and save their data into own tables, too.
171 *
172 * @param stdClass $definition
173 */
174 public function update_definition(stdClass $definition, $usermodified = null) {
175 global $DB, $USER;
176
177 if (is_null($usermodified)) {
178 $usermodified = $USER->id;
179 }
180
181 if ($this->definition) {
182 // the following fields can't be altered by the caller
183 $definition->id = $this->definition->id;
184 $definition->timemodified = time();
185 $definition->usermodified = $usermodified;
186 unset($definition->areaid);
187 unset($definition->method);
188 unset($definition->timecreated);
189
190 $DB->update_record('grading_definitions', $definition);
191
192 } else if ($this->definition === false) {
193 // the record did not existed when the controller was instantinated
194 // let us assume it still does not exist (this may throw exception
195 // in case of two users working on the same form definition at the same time)
196 unset($definition->id);
197 $definition->areaid = $this->areaid;
198 $definition->method = $this->get_method_name();
199 $definition->timecreated = time();
200 $definition->usercreated = $usermodified;
201 $definition->timemodified = $definition->timecreated;
202 $definition->usermodified = $definition->usercreated;
203 $definition->status = self::DEFINITION_STATUS_WORKINPROGRESS;
204
205 $DB->insert_record('grading_definitions', $definition);
206
207 } else {
208 // this should not happen - the record cache status is unknown, let us
209 // reload it and start again
210 $this->load_definition();
211 $this->update_definition($definition, $usermodified);
212 }
213 }
214
215 /**
216 * Makes sure there is a form instance for the given rater grading the given item
217 *
218 * Plugins will probably override/extend this and load additional data of how their
219 * forms are filled in one complex query.
220 *
221 * @todo this might actually become abstract method
222 * @param int $raterid
223 * @param int $itemid
224 * @return stdClass newly created or existing record from {grading_instances}
225 */
226 public function prepare_instance($raterid, $itemid) {
227 global $DB;
228
229 if (empty($this->definition)) {
230 throw new coding_exception('Attempting to prepare an instance of non-existing grading form');
231 }
232
233 $current = $DB->get_record('grading_instances', array(
234 'formid' => $this->definition->id,
235 'raterid' => $raterid,
236 'itemid' => $itemid), '*', IGNORE_MISSING);
237
238 if (empty($current)) {
239 $instance = new stdClass();
240 $instance->formid = $this->definition->id;
241 $instance->raterid = $raterid;
242 $instance->itemid = $itemid;
243 $instance->timemodified = time();
244 $instance->id = $DB->insert_record('grading_instances', $instance);
245 return $instance;
246
247 } else {
248 return $current;
249 }
250 }
251
252 /**
253 * Loads the form definition if it exists
3e43eff5 254 *
21d37aa6 255 * The default implementation just tries to load the record from the {grading_definitions}
3e43eff5
DM
256 * table. The plugins are likely to override this with a more complex query that loads
257 * all required data at once.
258 */
259 protected function load_definition() {
260 global $DB;
261
262 $this->definition = $DB->get_record('grading_definitions', array(
263 'areaid' => $this->areaid,
264 'method' => $this->get_method_name()), '*', IGNORE_MISSING);
265 }
266}
267
268
269/**
270 * Base class for all gradingform plugins renderers
271 */
272abstract class gradingform_renderer extends plugin_renderer_base {
273}
274
275
276/**
277 * Base class for all gradingform renderable widgets
278 */
279abstract class gradingform_widget implements renderable {
21d37aa6
DM
280
281 /** @var string unique identifier that can be used as the element id during the rendering, for example */
282 public $id;
283
284 /** @var gradingform_controller instance of the controller that created this widget */
285 public $controller;
286
287 /** @var array the widget options */
288 public $options;
289
290 /** @var stdClass the current instance data */
291 public $instance;
292
293 /**
294 * @param gradingform_controller the method controller that created this widget
295 */
296 public function __construct(gradingform_controller $controller, array $options, stdClass $instance) {
297 $this->id = uniqid(get_class($this));
298 $this->controller = $controller;
299 $this->options = $options;
300 $this->instance = $instance;
301 }
302}
303
304
305/**
306 * Base class for all gradingform renderable grading widgets
307 *
308 * Grading widget is the UI element that raters use when they interact with
309 * the grading form.
310 */
311abstract class gradingform_grading_widget extends gradingform_widget {
312}
313
314
315/**
316 * Base class for all gradingform renderable editor widgets
317 *
318 * Plugins that implement editor UI via its own renderable widgets should
319 * probably extend this. Editor widget is the UI element that course designers
320 * use when they design (define) the grading form.
321 */
322abstract class gradingform_editor_widget implements renderable {
9b8550f8 323}