MDL-30085 core_grades functions moved to correct location
[moodle.git] / grade / externallib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * External grading API
19  *
20  * @package    core_grading
21  * @since      Moodle 2.5
22  * @copyright  2013 Paul Charsley
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die;
28 // NOTE: add any new core_grades_ classes to /lib/classes/ directory.
30 /**
31  * core grading functions. Renamed to core_grading_external
32  *
33  * @since Moodle 2.5
34  * @deprecated since 2.6 See MDL-30085. Please do not use this class any more.
35  * @see core_grading_external
36  */
37 class core_grade_external extends external_api {
39     public static function get_definitions_parameters() {
40         return core_grading_external::get_definitions_parameters();
41     }
43     public static function get_definitions($cmids, $areaname, $activeonly = false) {
44         return core_grading_external::get_definitions($cmids, $areaname, $activeonly = false);
45     }
47     public static function get_definitions_returns() {
48         return core_grading_external::get_definitions_returns();
49     }
51 }
53 /**
54  * Core grades external functions
55  *
56  * @package    core_grades
57  * @category   external
58  * @copyright  2012 Andrew Davis
59  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
60  * @since Moodle 2.7
61  */
62 class core_grades_external extends external_api {
63     /**
64      * Returns description of method parameters
65      *
66      * @return external_function_parameters
67      * @since Moodle 2.7
68      */
69     public static function get_grades_parameters() {
70         return new external_function_parameters(
71             array(
72                 'courseid' => new external_value(PARAM_INT, 'id of course'),
73                 'component' => new external_value(
74                     PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''),
75                 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null),
76                 'userids' => new external_multiple_structure(
77                     new external_value(PARAM_INT, 'user ID'),
78                     'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array()
79                 )
80             )
81         );
82     }
84     /**
85      * Retrieve grade items and, optionally, student grades
86      *
87      * @param  int $courseid        Course id
88      * @param  string $component    Component name
89      * @param  int $activityid      Activity id
90      * @param  array  $userids      Array of user ids
91      * @return array                Array of grades
92      * @since Moodle 2.7
93      */
94     public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) {
95         global $CFG, $USER, $DB;
96         require_once("$CFG->libdir/gradelib.php");
98         $params = self::validate_parameters(self::get_grades_parameters(),
99             array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids));
101         $coursecontext = context_course::instance($params['courseid']);
103         try {
104             self::validate_context($coursecontext);
105         } catch (Exception $e) {
106             $exceptionparam = new stdClass();
107             $exceptionparam->message = $e->getMessage();
108             $exceptionparam->courseid = $params['courseid'];
109             throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
110         }
112         $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
114         $access = false;
115         if (has_capability('moodle/grade:viewall', $coursecontext)) {
116             // Can view all user's grades in this course.
117             $access = true;
119         } else if ($course->showgrades && count($params['userids']) == 1) {
120             // Course showgrades == students/parents can access grades.
122             if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) {
123                 // Student can view their own grades in this course.
124                 $access = true;
126             } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) {
127                 // User can view the grades of this user. Parent most probably.
128                 $access = true;
129             }
130         }
132         if (!$access) {
133             throw new moodle_exception('nopermissiontoviewgrades', 'error');
134         }
136         $itemtype = null;
137         $itemmodule = null;
138         if (!empty($params['component'])) {
139             list($itemtype, $itemmodule) = normalize_component($params['component']);
140         }
142         $cm = null;
143         if (!empty($itemmodule) && !empty($activityid)) {
144             if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
145                 throw new moodle_exception('invalidcoursemodule');
146             }
147         }
149         $cminstanceid = null;
150         if (!empty($cm)) {
151             $cminstanceid = $cm->instance;
152         }
153         $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']);
155         $acitivityinstances = null;
156         if (empty($cm)) {
157             // If we're dealing with multiple activites load all the module info.
158             $modinfo = get_fast_modinfo($params['courseid']);
159             $acitivityinstances = $modinfo->get_instances();
160         }
162         foreach ($grades->items as $gradeitem) {
163             if (!empty($cm)) {
164                 // If they only requested one activity we will already have the cm.
165                 $modulecm = $cm;
166             } else if (!empty($gradeitem->itemmodule)) {
167                 $modulecm = $acitivityinstances[$gradeitem->itemmodule][$gradeitem->iteminstance];
168             } else {
169                 // Course grade item.
170                 continue;
171             }
173             // Make student feedback ready for output.
174             foreach ($gradeitem->grades as $studentgrade) {
175                 if (!empty($studentgrade->feedback)) {
176                     list($studentgrade->feedback, $categoryinfo->feedbackformat) =
177                         external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
178                         $modulecm->id, $params['component'], 'feedback', null);
179                 }
180             }
181         }
183         // Convert from objects to arrays so all web service clients are supported.
184         // While we're doing that we also remove grades the current user can't see due to hiding.
185         $gradesarray = array();
186         $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid']));
188         $gradesarray['items'] = array();
189         foreach ($grades->items as $gradeitem) {
190             // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID.
191             $gradeiteminstance = self::get_grade_item(
192                 $course->id, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, 0);
193             if (!$canviewhidden && $gradeiteminstance->is_hidden()) {
194                 continue;
195             }
196             $gradeitemarray = (array)$gradeitem;
197             $gradeitemarray['grades'] = array();
199             if (!empty($gradeitem->grades)) {
200                 foreach ($gradeitem->grades as $studentid => $studentgrade) {
201                     $gradegradeinstance = grade_grade::fetch(
202                         array(
203                             'userid' => $studentid,
204                             'itemid' => $gradeiteminstance->id
205                         )
206                     );
207                     if (!$canviewhidden && $gradegradeinstance->is_hidden()) {
208                         continue;
209                     }
210                     $gradeitemarray['grades'][$studentid] = (array)$studentgrade;
211                     // Add the student ID as some WS clients can't access the array key.
212                     $gradeitemarray['grades'][$studentid]['userid'] = $studentid;
213                 }
214             }
216             // If they requested grades for multiple activities load the cm object now.
217             $modulecm = $cm;
218             if (empty($modulecm) && !empty($gradeiteminstance->itemmodule)) {
219                 $modulecm = $acitivityinstances[$gradeiteminstance->itemmodule][$gradeiteminstance->iteminstance];
220             }
221             if ($gradeiteminstance->itemtype == 'course') {
222                 $gradesarray['items']['course'] = $gradeitemarray;
223                 $gradesarray['items']['course']['activityid'] = 'course';
224             } else {
225                 $gradesarray['items'][$modulecm->id] = $gradeitemarray;
226                 // Add the activity ID as some WS clients can't access the array key.
227                 $gradesarray['items'][$modulecm->id]['activityid'] = $modulecm->id;
228             }
229         }
231         $gradesarray['outcomes'] = array();
232         foreach ($grades->outcomes as $outcome) {
233             $modulecm = $cm;
234             if (empty($modulecm)) {
235                 $modulecm = $acitivityinstances[$outcome->itemmodule][$outcome->iteminstance];
236             }
237             $gradesarray['outcomes'][$modulecm->id] = (array)$outcome;
238             $gradesarray['outcomes'][$modulecm->id]['activityid'] = $modulecm->id;
240             $gradesarray['outcomes'][$modulecm->id]['grades'] = array();
241             if (!empty($outcome->grades)) {
242                 foreach ($outcome->grades as $studentid => $studentgrade) {
243                     if (!$canviewhidden) {
244                         // Need to load the grade_grade object to check visibility.
245                         $gradeiteminstance = self::get_grade_item(
246                             $course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber);
247                         $gradegradeinstance = grade_grade::fetch(
248                             array(
249                                 'userid' => $studentid,
250                                 'itemid' => $gradeiteminstance->id
251                             )
252                         );
253                         // The grade grade may be legitimately missing if the student has no grade.
254                         if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) {
255                             continue;
256                         }
257                     }
258                     $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade;
260                     // Add the student ID into the grade structure as some WS clients can't access the key.
261                     $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid;
262                 }
263             }
264         }
266         return $gradesarray;
267     }
269     /**
270      * Get a grade item
271      * @param  int $courseid        Course id
272      * @param  string $itemtype     Item type
273      * @param  string $itemmodule   Item module
274      * @param  int $iteminstance    Item instance
275      * @param  int $itemnumber      Item number
276      * @return grade_item           A grade_item instance
277      */
278     private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) {
279         $gradeiteminstance = null;
280         if ($itemtype == 'course') {
281             $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype));
282         } else {
283             $gradeiteminstance = grade_item::fetch(
284                 array('courseid' => $courseid, 'itemtype' => $itemtype,
285                     'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber));
286         }
287         return $gradeiteminstance;
288     }
290     /**
291      * Returns description of method result value
292      *
293      * @return external_description
294      * @since Moodle 2.7
295      */
296     public static function get_grades_returns() {
297         return new external_single_structure(
298             array(
299                 'items'  => new external_multiple_structure(
300                     new external_single_structure(
301                         array(
302                             'activityid' => new external_value(
303                                 PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
304                             'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
305                             'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
306                             'name' => new external_value(PARAM_RAW, 'The module name'),
307                             'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'),
308                             'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'),
309                             'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'),
310                             'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'),
311                             'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'),
312                             'grades' => new external_multiple_structure(
313                                 new external_single_structure(
314                                     array(
315                                         'userid' => new external_value(
316                                             PARAM_INT, 'Student ID'),
317                                         'grade' => new external_value(
318                                             PARAM_FLOAT, 'Student grade'),
319                                         'locked' => new external_value(
320                                             PARAM_BOOL, 'Is the student\'s grade locked?'),
321                                         'hidden' => new external_value(
322                                             PARAM_BOOL, 'Is the student\'s grade hidden?'),
323                                         'overridden' => new external_value(
324                                             PARAM_BOOL, 'Is the student\'s grade overridden?'),
325                                         'feedback' => new external_value(
326                                             PARAM_RAW, 'Feedback from the grader'),
327                                         'feedbackformat' => new external_value(
328                                             PARAM_INT, 'The format of the feedback'),
329                                         'usermodified' => new external_value(
330                                             PARAM_INT, 'The ID of the last user to modify this student grade'),
331                                         'datesubmitted' => new external_value(
332                                             PARAM_INT, 'A timestamp indicating when the student submitted the activity'),
333                                         'dategraded' => new external_value(
334                                             PARAM_INT, 'A timestamp indicating when the assignment was grades'),
335                                         'str_grade' => new external_value(
336                                             PARAM_RAW, 'A string representation of the grade'),
337                                         'str_long_grade' => new external_value(
338                                             PARAM_RAW, 'A nicely formatted string representation of the grade'),
339                                         'str_feedback' => new external_value(
340                                             PARAM_TEXT, 'A string representation of the feedback from the grader'),
341                                     )
342                                 )
343                             ),
344                         )
345                     )
346                 ),
347                 'outcomes'  => new external_multiple_structure(
348                     new external_single_structure(
349                         array(
350                             'activityid' => new external_value(
351                                 PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
352                             'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
353                             'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
354                             'name' => new external_value(PARAM_RAW, 'The module name'),
355                             'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'),
356                             'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'),
357                             'grades' => new external_multiple_structure(
358                                 new external_single_structure(
359                                     array(
360                                         'userid' => new external_value(
361                                             PARAM_INT, 'Student ID'),
362                                         'grade' => new external_value(
363                                             PARAM_FLOAT, 'Student grade'),
364                                         'locked' => new external_value(
365                                             PARAM_BOOL, 'Is the student\'s grade locked?'),
366                                         'hidden' => new external_value(
367                                             PARAM_BOOL, 'Is the student\'s grade hidden?'),
368                                         'feedback' => new external_value(
369                                             PARAM_RAW, 'Feedback from the grader'),
370                                         'feedbackformat' => new external_value(
371                                             PARAM_INT, 'The feedback format'),
372                                         'usermodified' => new external_value(
373                                             PARAM_INT, 'The ID of the last user to modify this student grade'),
374                                         'str_grade' => new external_value(
375                                             PARAM_RAW, 'A string representation of the grade'),
376                                         'str_feedback' => new external_value(
377                                             PARAM_TEXT, 'A string representation of the feedback from the grader'),
378                                     )
379                                 )
380                             ),
381                         )
382                     ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL
383                 )
384             )
385         );
387     }
389     /**
390      * Returns description of method parameters
391      *
392      * @return external_function_parameters
393      * @since Moodle 2.7
394      */
395     public static function update_grades_parameters() {
396         return new external_function_parameters(
397             array(
398                 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'),
399                 'courseid' => new external_value(PARAM_INT, 'id of course'),
400                 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'),
401                 'activityid' => new external_value(PARAM_INT, 'The activity ID'),
402                 'itemnumber' => new external_value(
403                     PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'),
404                 'grades' => new external_multiple_structure(
405                     new external_single_structure(
406                         array(
407                             'studentid' => new external_value(PARAM_INT, 'Student ID'),
408                             'grade' => new external_value(PARAM_FLOAT, 'Student grade'),
409                             'str_feedback' => new external_value(
410                                 PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL),
411                         )
412                 ), 'Any student grades to alter', VALUE_OPTIONAL),
413                 'itemdetails' => new external_single_structure(
414                     array(
415                         'itemname' => new external_value(
416                             PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL),
417                         'idnumber' => new external_value(
418                             PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL),
419                         'gradetype' => new external_value(
420                             PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL),
421                         'grademax' => new external_value(
422                             PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL),
423                         'grademin' => new external_value(
424                             PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL),
425                         'scaleid' => new external_value(
426                             PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL),
427                         'multfactor' => new external_value(
428                             PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL),
429                         'plusfactor' => new external_value(
430                             PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL),
431                         'deleted' => new external_value(
432                             PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL),
433                         'hidden' => new external_value(
434                             PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL),
435                     ), 'Any grade item settings to alter', VALUE_OPTIONAL
436                 )
437             )
438         );
439     }
441     /**
442      * Update a grade item and, optionally, student grades
443      *
444      * @param  string $source       The source of the grade update
445      * @param  int $courseid        The course id
446      * @param  string $component    Component name
447      * @param  int $activityid      The activity id
448      * @param  int $itemnumber      The item number
449      * @param  array  $grades      Array of grades
450      * @param  array  $itemdetails Array of item details
451      * @return int                  A status flag
452      * @since Moodle 2.7
453      */
454     public static function update_grades($source, $courseid, $component, $activityid,
455         $itemnumber, $grades = array(), $itemdetails = array()) {
456         global $CFG;
458         require_once("$CFG->libdir/gradelib.php");
460         $params = self::validate_parameters(
461             self::update_grades_parameters(),
462             array(
463                 'source' => $source,
464                 'courseid' => $courseid,
465                 'component' => $component,
466                 'activityid' => $activityid,
467                 'itemnumber' => $itemnumber,
468                 'grades' => $grades,
469                 'itemdetails' => $itemdetails
470             )
471         );
473         list($itemtype, $itemmodule) = normalize_component($params['component']);
475         if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
476             throw new moodle_exception('invalidcoursemodule');
477         }
478         $iteminstance = $cm->instance;
480         $coursecontext = context_course::instance($params['courseid']);
482         try {
483             self::validate_context($coursecontext);
484         } catch (Exception $e) {
485             $exceptionparam = new stdClass();
486             $exceptionparam->message = $e->getMessage();
487             $exceptionparam->courseid = $params['courseid'];
488             throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
489         }
491         $hidinggrades = false;
492         $editinggradeitem = false;
493         $editinggrades = false;
495         $gradestructure = array();
496         foreach ($grades as $grade) {
497             $editinggrades = true;
498             $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']);
499         }
500         if (!empty($params['itemdetails'])) {
501             if (isset($params['itemdetails']['hidden'])) {
502                 $hidinggrades = true;
503             } else {
504                 $editinggradeitem = true;
505             }
506         }
508         if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) {
509             throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
510                 'moodle/grade:manage required to edit grade information');
511         }
512         if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) &&
513             !has_capability('moodle/grade:hide', $coursecontext)) {
514             throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
515                 'moodle/grade:hide required to hide grade items');
516         }
517         if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) {
518             throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
519                 'moodle/grade:edit required to edit grades');
520         }
522         return grade_update($params['source'], $params['courseid'], $itemtype,
523             $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']);
524     }
526     /**
527      * Returns description of method result value
528      *
529      * @return external_description
530      * @since Moodle 2.7
531      */
532     public static function update_grades_returns() {
533         return new external_single_structure(
534             array (
535                 'result' => new external_value(
536                     PARAM_INT,
537                     'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED
538                     as defined in lib/grade/constants.php')
539             )
540         );
541     }