MDL-30998: updated docblocks for backup of advanced grading
[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     /** undefined definition status */
35     const DEFINITION_STATUS_NULL = 0;
36     /** the form is currently being edited and is not ready for usage yet */
37     const DEFINITION_STATUS_DRAFT = 10;
38     /** the for was marked as ready for actual usage */
39     const DEFINITION_STATUS_READY = 20;
41     /** @var stdClass the context */
42     protected $context;
44     /** @var string the frankenstyle name of the component */
45     protected $component;
47     /** @var string the name of the gradable area */
48     protected $area;
50     /** @var int the id of the gradable area record */
51     protected $areaid;
53     /** @var stdClass|false the definition structure */
54     protected $definition = false;
56     /** @var array graderange array of valid grades for this area. Use set_grade_range and get_grade_range to access this */
57     private $graderange = null;
59     /** @var boolean|null cached result of function has_active_instances() */
60     protected $hasactiveinstances = null;
62     /**
63      * Do not instantinate this directly, use {@link grading_manager::get_controller()}
64      *
65      * @param stdClass $context the context of the form
66      * @param string $component the frankenstyle name of the component
67      * @param string $area the name of the gradable area
68      * @param int $areaid the id of the gradable area record
69      */
70     public function __construct(stdClass $context, $component, $area, $areaid) {
71         global $DB;
73         $this->context      = $context;
74         list($type, $name)  = normalize_component($component);
75         $this->component    = $type.'_'.$name;
76         $this->area         = $area;
77         $this->areaid       = $areaid;
79         $this->load_definition();
80     }
82     /**
83      * @return stdClass controller context
84      */
85     public function get_context() {
86         return $this->context;
87     }
89     /**
90      * @return string gradable component name
91      */
92     public function get_component() {
93         return $this->component;
94     }
96     /**
97      * @return string gradable area name
98      */
99     public function get_area() {
100         return $this->area;
101     }
103     /**
104      * @return int gradable area id
105      */
106     public function get_areaid() {
107         return $this->areaid;
108     }
110     /**
111      * Is the form definition record available?
112      *
113      * Note that this actually checks whether the process of defining the form ever started
114      * and not whether the form definition should be considered as final.
115      *
116      * @return boolean
117      */
118     public function is_form_defined() {
119         return ($this->definition !== false);
120     }
122     /**
123      * Is the grading form defined and ready for usage?
124      *
125      * @return boolean
126      */
127     public function is_form_available() {
128         return ($this->is_form_defined() && $this->definition->status == self::DEFINITION_STATUS_READY);
129     }
131     /**
132      * Is the grading form saved as a shared public template?
133      *
134      * @return boolean
135      */
136     public function is_shared_template() {
137         return ($this->get_context()->id == context_system::instance()->id
138             and $this->get_component() == 'core_grading');
139     }
141     /**
142      * Is the grading form owned by the given user?
143      *
144      * The form owner is the user who created this instance of the form.
145      *
146      * @param int $userid the user id to check, defaults to the current user
147      * @return boolean|null null if the form not defined yet, boolean otherwise
148      */
149     public function is_own_form($userid = null) {
150         global $USER;
152         if (!$this->is_form_defined()) {
153             return null;
154         }
155         if (is_null($userid)) {
156             $userid = $USER->id;
157         }
158         return ($this->definition->usercreated == $userid);
159     }
161     /**
162      * Returns a message why this form is unavailable. Maybe overriden by plugins to give more details.
163      * @see is_form_available()
164      *
165      * @return string
166      */
167     public function form_unavailable_notification() {
168         if ($this->is_form_available()) {
169             return null;
170         }
171         return get_string('gradingformunavailable', 'grading');
172     }
174     /**
175      * Returns URL of a page where the grading form can be defined and edited.
176      *
177      * @param moodle_url $returnurl optional URL of a page where the user should be sent once they are finished with editing
178      * @return moodle_url
179      */
180     public function get_editor_url(moodle_url $returnurl = null) {
182         $params = array('areaid' => $this->areaid);
184         if (!is_null($returnurl)) {
185             $params['returnurl'] = $returnurl->out(false);
186         }
188         return new moodle_url('/grade/grading/form/'.$this->get_method_name().'/edit.php', $params);
189     }
191     /**
192      * Extends the module settings navigation
193      *
194      * This function is called when the context for the page is an activity module with the
195      * FEATURE_ADVANCED_GRADING, the user has the permission moodle/grade:managegradingforms
196      * and there is an area with the active grading method set to the given plugin.
197      *
198      * @param settings_navigation $settingsnav {@link settings_navigation}
199      * @param navigation_node $node {@link navigation_node}
200      */
201     public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $node=null) {
202         // do not extend by default
203     }
205     /**
206      * Extends the module navigation
207      *
208      * This function is called when the context for the page is an activity module with the
209      * FEATURE_ADVANCED_GRADING and there is an area with the active grading method set to the given plugin.
210      *
211      * @param global_navigation $navigation {@link global_navigation}
212      * @param navigation_node $node {@link navigation_node}
213      */
214     public function extend_navigation(global_navigation $navigation, navigation_node $node=null) {
215         // do not extend by default
216     }
218     /**
219      * Returns the grading form definition structure
220      *
221      * @param boolean $force whether to force loading from DB even if it was already loaded
222      * @return stdClass|false definition data or false if the form is not defined yet
223      */
224     public function get_definition($force = false) {
225         if ($this->definition === false || $force) {
226             $this->load_definition();
227         }
228         return $this->definition;
229     }
231     /**
232      * Returns the form definition suitable for cloning into another area
233      *
234      * @param gradingform_controller $target the controller of the new copy
235      * @return stdClass definition structure to pass to the target's {@link update_definition()}
236      */
237     public function get_definition_copy(gradingform_controller $target) {
239         if (get_class($this) != get_class($target)) {
240             throw new coding_exception('The source and copy controller mismatch');
241         }
243         if ($target->is_form_defined()) {
244             throw new coding_exception('The target controller already contains a form definition');
245         }
247         $old = $this->get_definition();
248         // keep our id
249         $new = new stdClass();
250         $new->copiedfromid = $old->id;
251         $new->name = $old->name;
252         // once we support files embedded into the description, we will want to
253         // relink them into the new file area here (that is why we accept $target)
254         $new->description = $old->description;
255         $new->descriptionformat = $old->descriptionformat;
256         $new->options = $old->options;
257         $new->status = $old->status;
259         return $new;
260     }
262     /**
263      * Saves the defintion data into the database
264      *
265      * The implementation in this base class stores the common data into the record
266      * into the {grading_definition} table. The plugins are likely to extend this
267      * and save their data into own tables, too.
268      *
269      * @param stdClass $definition data containing values for the {grading_definition} table
270      * @param int|null $usermodified optional userid of the author of the definition, defaults to the current user
271      */
272     public function update_definition(stdClass $definition, $usermodified = null) {
273         global $DB, $USER;
275         if (is_null($usermodified)) {
276             $usermodified = $USER->id;
277         }
279         if (!empty($this->definition->id)) {
280             // prepare a record to be updated
281             $record = new stdClass();
282             // populate it with scalar values from the passed definition structure
283             foreach ($definition as $prop => $val) {
284                 if (is_array($val) or is_object($val)) {
285                     // probably plugin's data
286                     continue;
287                 }
288                 $record->{$prop} = $val;
289             }
290             // make sure we do not override some crucial values by accident
291             if (!empty($record->id) and $record->id != $this->definition->id) {
292                 throw new coding_exception('Attempting to update other definition record.');
293             }
294             $record->id = $this->definition->id;
295             unset($record->areaid);
296             unset($record->method);
297             unset($record->timecreated);
298             // set the modification flags
299             $record->timemodified = time();
300             $record->usermodified = $usermodified;
302             $DB->update_record('grading_definitions', $record);
304         } else if ($this->definition === false) {
305             // prepare a record to be inserted
306             $record = new stdClass();
307             // populate it with scalar values from the passed definition structure
308             foreach ($definition as $prop => $val) {
309                 if (is_array($val) or is_object($val)) {
310                     // probably plugin's data
311                     continue;
312                 }
313                 $record->{$prop} = $val;
314             }
315             // make sure we do not override some crucial values by accident
316             if (!empty($record->id)) {
317                 throw new coding_exception('Attempting to create a new record while there is already one existing.');
318             }
319             unset($record->id);
320             $record->areaid       = $this->areaid;
321             $record->method       = $this->get_method_name();
322             $record->timecreated  = time();
323             $record->usercreated  = $usermodified;
324             $record->timemodified = $record->timecreated;
325             $record->usermodified = $record->usercreated;
326             if (empty($record->status)) {
327                 $record->status = self::DEFINITION_STATUS_DRAFT;
328             }
329             if (empty($record->descriptionformat)) {
330                 $record->descriptionformat = FORMAT_MOODLE; // field can not be empty
331             }
333             $DB->insert_record('grading_definitions', $record);
335         } else {
336             throw new coding_exception('Unknown status of the cached definition record.');
337         }
338     }
340     /**
341      * Formats the definition description for display on page
342      *
343      * @return string
344      */
345     public function get_formatted_description() {
346         if (!isset($this->definition->description)) {
347             return '';
348         }
349         return format_text($this->definition->description, $this->definition->descriptionformat);
350     }
352     /**
353      * Returns the current instance (either with status ACTIVE or NEEDUPDATE) for this definition for the
354      * specified $raterid and $itemid (if multiple raters are allowed, or only for $itemid otherwise).
355      *
356      * @param int $raterid
357      * @param int $itemid
358      * @param boolean $idonly
359      * @return mixed if $idonly=true returns id of the found instance, otherwise returns the instance object
360      */
361     public function get_current_instance($raterid, $itemid, $idonly = false) {
362         global $DB;
363         $params = array(
364                 'definitionid'  => $this->definition->id,
365                 'itemid' => $itemid,
366                 'status1'  => gradingform_instance::INSTANCE_STATUS_ACTIVE,
367                 'status2'  => gradingform_instance::INSTANCE_STATUS_NEEDUPDATE);
368         $select = 'definitionid=:definitionid and itemid=:itemid and (status=:status1 or status=:status2)';
369         if (false /* TODO $manager->allow_multiple_raters() */) {
370             $select .= ' and raterid=:raterid';
371             $params['raterid'] = $raterid;
372         }
373         if ($idonly) {
374             if ($current = $DB->get_record_select('grading_instances', $select, $params, 'id', IGNORE_MISSING)) {
375                 return $current->id;
376             }
377         } else {
378             if ($current = $DB->get_record_select('grading_instances', $select, $params, '*', IGNORE_MISSING)) {
379                 return $this->get_instance($current);
380             }
381         }
382         return null;
383     }
385     /**
386      * Returns list of ACTIVE instances for the specified $itemid
387      * (intentionally does not return instances with status NEEDUPDATE)
388      *
389      * @param int $itemid
390      * @return array of gradingform_instance objects
391      */
392     public function get_active_instances($itemid) {
393         global $DB;
394         $conditions = array('definitionid'  => $this->definition->id,
395                     'itemid' => $itemid,
396                     'status'  => gradingform_instance::INSTANCE_STATUS_ACTIVE);
397         $records = $DB->get_recordset('grading_instances', $conditions);
398         $rv = array();
399         foreach ($records as $record) {
400             $rv[] = $this->get_instance($record);
401         }
402         return $rv;
403     }
405     /**
406      * Returns true if there are already people who has been graded on this definition.
407      * In this case plugins may restrict changes of the grading definition
408      *
409      * @return boolean
410      */
411     public function has_active_instances() {
412         global $DB;
413         if (empty($this->definition->id)) {
414             return false;
415         }
416         if ($this->hasactiveinstances === null) {
417             $conditions = array('definitionid'  => $this->definition->id,
418                         'status'  => gradingform_instance::INSTANCE_STATUS_ACTIVE);
419             $this->hasactiveinstances = $DB->record_exists('grading_instances', $conditions);
420         }
421         return $this->hasactiveinstances;
422     }
424     /**
425      * Returns the object of type gradingform_XXX_instance (where XXX is the plugin method name)
426      *
427      * @param mixed $instance id or row from grading_isntances table
428      * @return gradingform_instance
429      */
430     protected function get_instance($instance) {
431         global $DB;
432         if (is_scalar($instance)) {
433             // instance id is passed as parameter
434             $instance = $DB->get_record('grading_instances', array('id'  => $instance), '*', MUST_EXIST);
435         }
436         if ($instance) {
437             $class = 'gradingform_'. $this->get_method_name(). '_instance';
438             return new $class($this, $instance);
439         }
440         return null;
441     }
443     /**
444      * This function is invoked when user (teacher) starts grading.
445      * It creates and returns copy of the current ACTIVE instance if it exists. If this is the
446      * first grading attempt, a new instance is created.
447      * The status of the returned instance is INCOMPLETE
448      *
449      * @param int $raterid
450      * @param int $itemid
451      * @return gradingform_instance
452      */
453     public function create_instance($raterid, $itemid = null) {
455         // first find if there is already an active instance for this itemid
456         if ($itemid && $current = $this->get_current_instance($raterid, $itemid)) {
457             return $this->get_instance($current->copy($raterid, $itemid));
458         } else {
459             $class = 'gradingform_'. $this->get_method_name(). '_instance';
460             return $this->get_instance($class::create_new($this->definition->id, $raterid, $itemid));
461         }
462     }
464     /**
465      * If instanceid is specified and grading instance exists and it is created by this rater for
466      * this item, this instance is returned.
467      * Otherwise new instance is created for the specified rater and itemid
468      *
469      * @param int $instanceid
470      * @param int $raterid
471      * @param int $itemid
472      * @return gradingform_instance
473      */
474     public function get_or_create_instance($instanceid, $raterid, $itemid) {
475         global $DB;
476         if ($instanceid &&
477                 $instance = $DB->get_record('grading_instances', array('id'  => $instanceid, 'raterid' => $raterid, 'itemid' => $itemid), '*', IGNORE_MISSING)) {
478             return $this->get_instance($instance);
479         }
480         return $this->create_instance($raterid, $itemid);
481     }
483     /**
484      * Returns the HTML code displaying the preview of the grading form
485      *
486      * Plugins are forced to override this. Ideally they should delegate
487      * the task to their own renderer.
488      *
489      * @param moodle_page $page the target page
490      * @return string
491      */
492     abstract public function render_preview(moodle_page $page);
494     /**
495      * Deletes the form definition and all the associated data
496      *
497      * @see delete_plugin_definition()
498      * @return void
499      */
500     public function delete_definition() {
501         global $DB;
503         if (!$this->is_form_defined()) {
504             // nothing to do
505             return;
506         }
508         // firstly, let the plugin delete everything from their own tables
509         $this->delete_plugin_definition();
510         // then, delete all instances left
511         $DB->delete_records('grading_instances', array('definitionid' => $this->definition->id));
512         // finally, delete the main definition record
513         $DB->delete_records('grading_definitions', array('id' => $this->definition->id));
515         $this->definition = false;
516     }
518     /**
519      * Prepare the part of the search query to append to the FROM statement
520      *
521      * @param string $gdid the alias of grading_definitions.id column used by the caller
522      * @return string
523      */
524     public static function sql_search_from_tables($gdid) {
525         return '';
526     }
528     /**
529      * Prepare the parts of the SQL WHERE statement to search for the given token
530      *
531      * The returned array cosists of the list of SQL comparions and the list of
532      * respective parameters for the comparisons. The returned chunks will be joined
533      * with other conditions using the OR operator.
534      *
535      * @param string $token token to search for
536      * @return array
537      */
538     public static function sql_search_where($token) {
540         $subsql = array();
541         $params = array();
543         return array($subsql, $params);
544     }
546     ////////////////////////////////////////////////////////////////////////////
548     /**
549      * Loads the form definition if it exists
550      *
551      * The default implementation just tries to load the record from the {grading_definitions}
552      * table. The plugins are likely to override this with a more complex query that loads
553      * all required data at once.
554      */
555     protected function load_definition() {
556         global $DB;
557         $this->definition = $DB->get_record('grading_definitions', array(
558             'areaid' => $this->areaid,
559             'method' => $this->get_method_name()), '*', IGNORE_MISSING);
560     }
562     /**
563      * Deletes all plugin data associated with the given form definiton
564      *
565      * @see delete_definition()
566      */
567     abstract protected function delete_plugin_definition();
569     /**
570      * @return string the name of the grading method plugin, eg 'rubric'
571      * @see PARAM_PLUGIN
572      */
573     protected function get_method_name() {
574         if (preg_match('/^gradingform_([a-z][a-z0-9_]*[a-z0-9])_controller$/', get_class($this), $matches)) {
575             return $matches[1];
576         } else {
577             throw new coding_exception('Invalid class name');
578         }
579     }
581     /**
582      * Returns html code to be included in student's feedback.
583      *
584      * @param moodle_page $page
585      * @param int $itemid
586      * @param array $gradinginfo result of function grade_get_grades if plugin want to use some of their info
587      * @param string $defaultcontent default string to be returned if no active grading is found or for some reason can not be shown to a user
588      * @param boolean $cangrade whether current user has capability to grade in this context
589      * @return string
590      */
591     public function render_grade($page, $itemid, $gradinginfo, $defaultcontent, $cangrade) {
592         return $defaultcontent;
593     }
595     /**
596      * Sets the range of grades used in this area. This is usually either range like 0-100
597      * or the scale where keys start from 1. Typical use:
598      * $controller->set_grade_range(make_grades_menu($gradingtype));
599      *
600      * @param array $graderange
601      */
602     public final function set_grade_range(array $graderange) {
603         $this->graderange = $graderange;
604     }
606     /**
607      * Returns the range of grades used in this area
608      *
609      * @return array
610      */
611     public final function get_grade_range() {
612         if (empty($this->graderange)) {
613             return array();
614         }
615         return $this->graderange;
616     }
619 /**
620  * Class to manage one grading instance. Stores information and performs actions like
621  * update, copy, validate, submit, etc.
622  *
623  * @copyright  2011 Marina Glancy
624  */
625 abstract class gradingform_instance {
626     const INSTANCE_STATUS_ACTIVE = 1;
627     const INSTANCE_STATUS_NEEDUPDATE = 2;
628     const INSTANCE_STATUS_INCOMPLETE = 0;
629     const INSTANCE_STATUS_ARCHIVE = 3;
631     /** @var stdClass record from table grading_instances */
632     protected $data;
633     /** @var gradingform_controller link to the corresponding controller */
634     protected $controller;
636     /**
637      * Creates an instance
638      *
639      * @param gradingform_controller $controller
640      * @param stdClass $data
641      */
642     public function __construct($controller, $data) {
643         $this->data = (object)$data;
644         $this->controller = $controller;
645     }
647     /**
648      * Creates a new empty instance in DB and mark its status as INCOMPLETE
649      *
650      * @param int $definitionid
651      * @param int $raterid
652      * @param int $itemid
653      * @return int id of the created instance
654      */
655     public static function create_new($definitionid, $raterid, $itemid) {
656         global $DB;
657         $instance = new stdClass();
658         $instance->definitionid = $definitionid;
659         $instance->raterid = $raterid;
660         $instance->itemid = $itemid;
661         $instance->status = self::INSTANCE_STATUS_INCOMPLETE;
662         $instance->timemodified = time();
663         $instance->feedbackformat = FORMAT_MOODLE;
664         $instanceid = $DB->insert_record('grading_instances', $instance);
665         return $instanceid;
666     }
668     /**
669      * Duplicates the instance before editing (optionally substitutes raterid and/or itemid with
670      * the specified values)
671      * Plugins may want to override this function to copy data from additional tables as well
672      *
673      * @param int $raterid value for raterid in the duplicate
674      * @param int $itemid value for itemid in the duplicate
675      * @return int id of the new instance
676      */
677     public function copy($raterid, $itemid) {
678         global $DB;
679         $data = (array)$this->data; // Cast to array to make a copy
680         unset($data['id']);
681         $data['raterid'] = $raterid;
682         $data['itemid'] = $itemid;
683         $data['timemodified'] = time();
684         $data['status'] = self::INSTANCE_STATUS_INCOMPLETE;
685         $instanceid = $DB->insert_record('grading_instances', $data);
686         return $instanceid;
687     }
689     /**
690      * Returns the current (active or needupdate) instance for the same raterid and itemid as this
691      * instance. This function is useful to find the status of the currently modified instance
692      *
693      * @return gradingform_instance
694      */
695     public function get_current_instance() {
696         if ($this->get_status() == self::INSTANCE_STATUS_ACTIVE || $this->get_status() == self::INSTANCE_STATUS_NEEDUPDATE) {
697             return $this;
698         }
699         return $this->get_controller()->get_current_instance($this->data->raterid, $this->data->itemid);
700     }
702     /**
703      * Returns the controller
704      *
705      * @return gradingform_controller
706      */
707     public function get_controller() {
708         return $this->controller;
709     }
711     /**
712      * Returns the specified element from object $this->data
713      *
714      * @param string $key
715      * @return mixed
716      */
717     public function get_data($key) {
718         if (isset($this->data->$key)) {
719             return $this->data->$key;
720         }
721         return null;
722     }
724     /**
725      * Returns instance id
726      *
727      * @return int
728      */
729     public function get_id() {
730         return $this->get_data('id');
731     }
733     /**
734      * Returns instance status
735      *
736      * @return int
737      */
738     public function get_status() {
739         return $this->get_data('status');
740     }
742     /**
743      * Marks the instance as ACTIVE and current active instance (if exists) as ARCHIVE
744      */
745     protected function make_active() {
746         global $DB;
747         if ($this->data->status == self::INSTANCE_STATUS_ACTIVE) {
748             // already active
749             return;
750         }
751         if (empty($this->data->itemid)) {
752             throw new coding_exception('You cannot mark active the grading instance without itemid');
753         }
754         $currentid = $this->get_controller()->get_current_instance($this->data->raterid, $this->data->itemid, true);
755         if ($currentid && $currentid != $this->get_id()) {
756             $DB->update_record('grading_instances', array('id' => $currentid, 'status' => self::INSTANCE_STATUS_ARCHIVE));
757         }
758         $DB->update_record('grading_instances', array('id' => $this->get_id(), 'status' => self::INSTANCE_STATUS_ACTIVE));
759         $this->data->status = self::INSTANCE_STATUS_ACTIVE;
760     }
762     /**
763      * Deletes this (INCOMPLETE) instance from database. This function is invoked on cancelling the
764      * grading form and/or during cron cleanup.
765      * Plugins using additional tables must override this method to remove additional data.
766      * Note that if the teacher just closes the window or presses 'Back' button of the browser,
767      * this function is not invoked.
768      */
769     public function cancel() {
770         global $DB;
771         // TODO what if we happen delete the ACTIVE instance, shall we rollback to the last ARCHIVE? or throw an exception?
772         // TODO create cleanup cron
773         $DB->delete_records('grading_instances', array('id' => $this->get_id()));
774     }
776     /**
777      * Updates the instance with the data received from grading form. This function may be
778      * called via AJAX when grading is not yet completed, so it does not change the
779      * status of the instance.
780      *
781      * @param array $elementvalue
782      */
783     public function update($elementvalue) {
784         global $DB;
785         $newdata = new stdClass();
786         $newdata->id = $this->get_id();
787         $newdata->timemodified = time();
788         if (isset($elementvalue['itemid']) && $elementvalue['itemid'] != $this->data->itemid) {
789             $newdata->itemid = $elementvalue['itemid'];
790         }
791         // TODO also update: rawgrade, feedback, feedbackformat
792         $DB->update_record('grading_instances', $newdata);
793         foreach ($newdata as $key => $value) {
794             $this->data->$key = $value;
795         }
796     }
798     /**
799      * Calculates the grade to be pushed to the gradebook
800      *
801      * @return int the valid grade from $this->get_controller()->get_grade_range()
802      */
803     abstract public function get_grade();
805     /**
806      * Called when teacher submits the grading form:
807      * updates the instance in DB, marks it as ACTIVE and returns the grade to be pushed to the gradebook.
808      * $itemid must be specified here (it was not required when the instance was
809      * created, because it might not existed in draft)
810      *
811      * @param array $elementvalue
812      * @param int $itemid
813      * @return int the grade on 0-100 scale
814      */
815     public function submit_and_get_grade($elementvalue, $itemid) {
816         $elementvalue['itemid'] = $itemid;
817         $this->update($elementvalue);
818         $this->make_active();
819         return $this->get_grade();
820     }
823     /**
824      * Returns html for form element of type 'grading'. If there is a form input element
825      * it must have the name $gradingformelement->getName().
826      * If there are more than one input elements they MUST be elements of array with
827      * name $gradingformelement->getName().
828      * Example: {NAME}[myelement1], {NAME}[myelement2][sub1], {NAME}[myelement2][sub2], etc.
829      * ( {NAME} is a shortcut for $gradingformelement->getName() )
830      * After submitting the form the value of $_POST[{NAME}] is passed to the functions
831      * validate_grading_element() and submit_and_get_grade()
832      *
833      * Plugins may use $gradingformelement->getValue() to get the value passed on previous
834      * form submit
835      *
836      * When forming html it is a plugin's responsibility to analyze flags
837      * $gradingformelement->_flagFrozen and $gradingformelement->_persistantFreeze:
838      *
839      * (_flagFrozen == false) => form element is editable
840      *
841      * (_flagFrozen == false && _persistantFreeze == true) => form element is not editable
842      * but all values are passed as hidden elements
843      *
844      * (_flagFrozen == false && _persistantFreeze == false) => form element is not editable
845      * and no values are passed as hidden elements
846      *
847      * Plugins are welcome to use AJAX in the form element. But it is strongly recommended
848      * that the grading only becomes active when teacher presses 'Submit' button (the
849      * method submit_and_get_grade() is invoked)
850      *
851      * Also client-side JS validation may be implemented here
852      *
853      * @see MoodleQuickForm_grading in lib/form/grading.php
854      *
855      * @param moodle_page $page
856      * @param MoodleQuickForm_grading $gradingformelement
857      * @return string
858      */
859     abstract function render_grading_element($page, $gradingformelement);
861     /**
862      * Server-side validation of the data received from grading form.
863      *
864      * @param mixed $elementvalue is the scalar or array received in $_POST
865      * @return boolean true if the form data is validated and contains no errors
866      */
867     public function validate_grading_element($elementvalue) {
868         return true;
869     }
871     /**
872      * Returns the error message displayed if validation failed.
873      * If plugin wants to display custom message, the empty string should be returned here
874      * and the custom message should be output in render_grading_element()
875      *
876      * @see validate_grading_element()
877      * @return string
878      */
879     public function default_validation_error_message() {
880         return '';
881     }