weekly release 3.1dev
[moodle.git] / lib / classes / grades_external.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  * Core grades external functions
19  *
20  * @package    core_grades
21  * @copyright  2012 Andrew Davis
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  * @since Moodle 2.7
24  */
26 defined('MOODLE_INTERNAL') || die;
28 require_once("$CFG->libdir/externallib.php");
29 require_once("$CFG->libdir/gradelib.php");
30 require_once("$CFG->dirroot/grade/querylib.php");
32 /**
33  * core grades functions
34  */
35 class core_grades_external extends external_api {
36     /**
37      * Returns description of method parameters
38      *
39      * @return external_function_parameters
40      * @since Moodle 2.7
41      */
42     public static function get_grades_parameters() {
43         return new external_function_parameters(
44             array(
45                 'courseid' => new external_value(PARAM_INT, 'id of course'),
46                 'component' => new external_value(
47                     PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''),
48                 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null),
49                 'userids' => new external_multiple_structure(
50                     new external_value(PARAM_INT, 'user ID'),
51                     'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array()
52                 )
53             )
54         );
55     }
57     /**
58      * Returns student course total grade and grades for activities.
59      * This function does not return category or manual items.
60      * This function is suitable for managers or teachers not students.
61      *
62      * @param  int $courseid        Course id
63      * @param  string $component    Component name
64      * @param  int $activityid      Activity id
65      * @param  array  $userids      Array of user ids
66      * @return array                Array of grades
67      * @since Moodle 2.7
68      */
69     public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) {
70         global $CFG, $USER, $DB;
72         $params = self::validate_parameters(self::get_grades_parameters(),
73             array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids));
75         $gradesarray = array(
76             'items'     => array(),
77             'outcomes'  => array()
78         );
80         $coursecontext = context_course::instance($params['courseid']);
82         try {
83             self::validate_context($coursecontext);
84         } catch (Exception $e) {
85             $exceptionparam = new stdClass();
86             $exceptionparam->message = $e->getMessage();
87             $exceptionparam->courseid = $params['courseid'];
88             throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
89         }
91         require_capability('moodle/grade:viewhidden', $coursecontext);
93         $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
95         $access = false;
96         if (has_capability('moodle/grade:viewall', $coursecontext)) {
97             // Can view all user's grades in this course.
98             $access = true;
100         } else if ($course->showgrades && count($params['userids']) == 1) {
101             // Course showgrades == students/parents can access grades.
103             if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) {
104                 // Student can view their own grades in this course.
105                 $access = true;
107             } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) {
108                 // User can view the grades of this user. Parent most probably.
109                 $access = true;
110             }
111         }
113         if (!$access) {
114             throw new moodle_exception('nopermissiontoviewgrades', 'error');
115         }
117         $itemtype = null;
118         $itemmodule = null;
119         $iteminstance = null;
121         if (!empty($params['component'])) {
122             list($itemtype, $itemmodule) = normalize_component($params['component']);
123         }
125         $cm = null;
126         if (!empty($itemmodule) && !empty($params['activityid'])) {
127             if (!$cm = get_coursemodule_from_id($itemmodule, $params['activityid'])) {
128                 throw new moodle_exception('invalidcoursemodule');
129             }
130             $iteminstance = $cm->instance;
131         }
133         // Load all the module info.
134         $modinfo = get_fast_modinfo($params['courseid']);
135         $activityinstances = $modinfo->get_instances();
137         $gradeparams = array('courseid' => $params['courseid']);
138         if (!empty($itemtype)) {
139             $gradeparams['itemtype'] = $itemtype;
140         }
141         if (!empty($itemmodule)) {
142             $gradeparams['itemmodule'] = $itemmodule;
143         }
144         if (!empty($iteminstance)) {
145             $gradeparams['iteminstance'] = $iteminstance;
146         }
148         if ($activitygrades = grade_item::fetch_all($gradeparams)) {
149             $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid']));
151             foreach ($activitygrades as $activitygrade) {
153                 if ($activitygrade->itemtype != 'course' and $activitygrade->itemtype != 'mod') {
154                     // This function currently only supports course and mod grade items. Manual and category not supported.
155                     continue;
156                 }
158                 $context = $coursecontext;
160                 if ($activitygrade->itemtype == 'course') {
161                     $item = grade_get_course_grades($course->id, $params['userids']);
162                     $item->itemnumber = 0;
164                     $grades = new stdClass;
165                     $grades->items = array($item);
166                     $grades->outcomes = array();
168                 } else {
169                     $cm = $activityinstances[$activitygrade->itemmodule][$activitygrade->iteminstance];
170                     $instance = $cm->instance;
171                     $context = context_module::instance($cm->id, IGNORE_MISSING);
173                     $grades = grade_get_grades($params['courseid'], $activitygrade->itemtype,
174                                                 $activitygrade->itemmodule, $instance, $params['userids']);
175                 }
177                 // Convert from objects to arrays so all web service clients are supported.
178                 // While we're doing that we also remove grades the current user can't see due to hiding.
179                 foreach ($grades->items as $gradeitem) {
180                     // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID.
181                     $gradeiteminstance = self::get_grade_item(
182                         $course->id, $activitygrade->itemtype, $activitygrade->itemmodule, $activitygrade->iteminstance, 0);
183                     if (!$canviewhidden && $gradeiteminstance->is_hidden()) {
184                         continue;
185                     }
187                     // Format mixed bool/integer parameters.
188                     $gradeitem->hidden = (empty($gradeitem->hidden)) ? 0 : $gradeitem->hidden;
189                     $gradeitem->locked = (empty($gradeitem->locked)) ? 0 : $gradeitem->locked;
191                     $gradeitemarray = (array)$gradeitem;
192                     $gradeitemarray['grades'] = array();
194                     if (!empty($gradeitem->grades)) {
195                         foreach ($gradeitem->grades as $studentid => $studentgrade) {
196                             if (!$canviewhidden) {
197                                 // Need to load the grade_grade object to check visibility.
198                                 $gradegradeinstance = grade_grade::fetch(
199                                     array(
200                                         'userid' => $studentid,
201                                         'itemid' => $gradeiteminstance->id
202                                     )
203                                 );
204                                 // The grade grade may be legitimately missing if the student has no grade.
205                                 if (!empty($gradegradeinstance) && $gradegradeinstance->is_hidden()) {
206                                     continue;
207                                 }
208                             }
210                             // Format mixed bool/integer parameters.
211                             $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
212                             $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
213                             $studentgrade->overridden = (empty($studentgrade->overridden)) ? 0 : $studentgrade->overridden;
215                             if ($gradeiteminstance->itemtype != 'course' and !empty($studentgrade->feedback)) {
216                                 list($studentgrade->feedback, $studentgrade->feedbackformat) =
217                                     external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
218                                     $context->id, $params['component'], 'feedback', null);
219                             }
221                             $gradeitemarray['grades'][$studentid] = (array)$studentgrade;
222                             // Add the student ID as some WS clients can't access the array key.
223                             $gradeitemarray['grades'][$studentid]['userid'] = $studentid;
224                         }
225                     }
227                     if ($gradeiteminstance->itemtype == 'course') {
228                         $gradesarray['items']['course'] = $gradeitemarray;
229                         $gradesarray['items']['course']['activityid'] = 'course';
230                     } else {
231                         $gradesarray['items'][$cm->id] = $gradeitemarray;
232                         // Add the activity ID as some WS clients can't access the array key.
233                         $gradesarray['items'][$cm->id]['activityid'] = $cm->id;
234                     }
235                 }
237                 foreach ($grades->outcomes as $outcome) {
238                     // Format mixed bool/integer parameters.
239                     $outcome->hidden = (empty($outcome->hidden)) ? 0 : $outcome->hidden;
240                     $outcome->locked = (empty($outcome->locked)) ? 0 : $outcome->locked;
242                     $gradesarray['outcomes'][$cm->id] = (array)$outcome;
243                     $gradesarray['outcomes'][$cm->id]['activityid'] = $cm->id;
245                     $gradesarray['outcomes'][$cm->id]['grades'] = array();
246                     if (!empty($outcome->grades)) {
247                         foreach ($outcome->grades as $studentid => $studentgrade) {
248                             if (!$canviewhidden) {
249                                 // Need to load the grade_grade object to check visibility.
250                                 $gradeiteminstance = self::get_grade_item($course->id, $activitygrade->itemtype,
251                                                                            $activitygrade->itemmodule, $activitygrade->iteminstance,
252                                                                            $activitygrade->itemnumber);
253                                 $gradegradeinstance = grade_grade::fetch(
254                                     array(
255                                         'userid' => $studentid,
256                                         'itemid' => $gradeiteminstance->id
257                                     )
258                                 );
259                                 // The grade grade may be legitimately missing if the student has no grade.
260                                 if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) {
261                                     continue;
262                                 }
263                             }
265                             // Format mixed bool/integer parameters.
266                             $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
267                             $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
269                             if (!empty($studentgrade->feedback)) {
270                                 list($studentgrade->feedback, $studentgrade->feedbackformat) =
271                                     external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
272                                     $context->id, $params['component'], 'feedback', null);
273                             }
275                             $gradesarray['outcomes'][$cm->id]['grades'][$studentid] = (array)$studentgrade;
277                             // Add the student ID into the grade structure as some WS clients can't access the key.
278                             $gradesarray['outcomes'][$cm->id]['grades'][$studentid]['userid'] = $studentid;
279                         }
280                     }
281                 }
282             }
283         }
285         return $gradesarray;
286     }
288     /**
289      * Get a grade item
290      * @param  int $courseid        Course id
291      * @param  string $itemtype     Item type
292      * @param  string $itemmodule   Item module
293      * @param  int $iteminstance    Item instance
294      * @param  int $itemnumber      Item number
295      * @return grade_item           A grade_item instance
296      */
297     private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) {
298         $gradeiteminstance = null;
299         if ($itemtype == 'course') {
300             $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype));
301         } else {
302             $gradeiteminstance = grade_item::fetch(
303                 array('courseid' => $courseid, 'itemtype' => $itemtype,
304                     'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber));
305         }
306         return $gradeiteminstance;
307     }
309     /**
310      * Returns description of method result value
311      *
312      * @return external_description
313      * @since Moodle 2.7
314      */
315     public static function get_grades_returns() {
316         return new external_single_structure(
317             array(
318                 'items'  => new external_multiple_structure(
319                     new external_single_structure(
320                         array(
321                             'activityid' => new external_value(
322                                 PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
323                             'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
324                             'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
325                             'name' => new external_value(PARAM_RAW, 'The module name'),
326                             'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'),
327                             'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'),
328                             'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'),
329                             'locked' => new external_value(PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
330                             'hidden' => new external_value(PARAM_INT, '0 means not hidden, > 1 is a date to hide until'),
331                             'grades' => new external_multiple_structure(
332                                 new external_single_structure(
333                                     array(
334                                         'userid' => new external_value(
335                                             PARAM_INT, 'Student ID'),
336                                         'grade' => new external_value(
337                                             PARAM_FLOAT, 'Student grade'),
338                                         'locked' => new external_value(
339                                             PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
340                                         'hidden' => new external_value(
341                                             PARAM_INT, '0 means not hidden, 1 hidden, > 1 is a date to hide until'),
342                                         'overridden' => new external_value(
343                                             PARAM_INT, '0 means not overridden, > 1 means overridden'),
344                                         'feedback' => new external_value(
345                                             PARAM_RAW, 'Feedback from the grader'),
346                                         'feedbackformat' => new external_value(
347                                             PARAM_INT, 'The format of the feedback'),
348                                         'usermodified' => new external_value(
349                                             PARAM_INT, 'The ID of the last user to modify this student grade'),
350                                         'datesubmitted' => new external_value(
351                                             PARAM_INT, 'A timestamp indicating when the student submitted the activity'),
352                                         'dategraded' => new external_value(
353                                             PARAM_INT, 'A timestamp indicating when the assignment was grades'),
354                                         'str_grade' => new external_value(
355                                             PARAM_RAW, 'A string representation of the grade'),
356                                         'str_long_grade' => new external_value(
357                                             PARAM_RAW, 'A nicely formatted string representation of the grade'),
358                                         'str_feedback' => new external_value(
359                                             PARAM_RAW, 'A formatted string representation of the feedback from the grader'),
360                                     )
361                                 )
362                             ),
363                         )
364                     )
365                 ),
366                 'outcomes'  => new external_multiple_structure(
367                     new external_single_structure(
368                         array(
369                             'activityid' => new external_value(
370                                 PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
371                             'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
372                             'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
373                             'name' => new external_value(PARAM_RAW, 'The module name'),
374                             'locked' => new external_value(PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
375                             'hidden' => new external_value(PARAM_INT, '0 means not hidden, > 1 is a date to hide until'),
376                             'grades' => new external_multiple_structure(
377                                 new external_single_structure(
378                                     array(
379                                         'userid' => new external_value(
380                                             PARAM_INT, 'Student ID'),
381                                         'grade' => new external_value(
382                                             PARAM_FLOAT, 'Student grade'),
383                                         'locked' => new external_value(
384                                             PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
385                                         'hidden' => new external_value(
386                                             PARAM_INT, '0 means not hidden, 1 hidden, > 1 is a date to hide until'),
387                                         'feedback' => new external_value(
388                                             PARAM_RAW, 'Feedback from the grader'),
389                                         'feedbackformat' => new external_value(
390                                             PARAM_INT, 'The feedback format'),
391                                         'usermodified' => new external_value(
392                                             PARAM_INT, 'The ID of the last user to modify this student grade'),
393                                         'str_grade' => new external_value(
394                                             PARAM_RAW, 'A string representation of the grade'),
395                                         'str_feedback' => new external_value(
396                                             PARAM_RAW, 'A formatted string representation of the feedback from the grader'),
397                                     )
398                                 )
399                             ),
400                         )
401                     ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL
402                 )
403             )
404         );
406     }
408     /**
409      * Returns description of method parameters
410      *
411      * @return external_function_parameters
412      * @since Moodle 2.7
413      */
414     public static function update_grades_parameters() {
415         return new external_function_parameters(
416             array(
417                 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'),
418                 'courseid' => new external_value(PARAM_INT, 'id of course'),
419                 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'),
420                 'activityid' => new external_value(PARAM_INT, 'The activity ID'),
421                 'itemnumber' => new external_value(
422                     PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'),
423                 'grades' => new external_multiple_structure(
424                     new external_single_structure(
425                         array(
426                             'studentid' => new external_value(PARAM_INT, 'Student ID'),
427                             'grade' => new external_value(PARAM_FLOAT, 'Student grade'),
428                             'str_feedback' => new external_value(
429                                 PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL),
430                         )
431                 ), 'Any student grades to alter', VALUE_DEFAULT, array()),
432                 'itemdetails' => new external_single_structure(
433                     array(
434                         'itemname' => new external_value(
435                             PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL),
436                         'idnumber' => new external_value(
437                             PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL),
438                         'gradetype' => new external_value(
439                             PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL),
440                         'grademax' => new external_value(
441                             PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL),
442                         'grademin' => new external_value(
443                             PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL),
444                         'scaleid' => new external_value(
445                             PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL),
446                         'multfactor' => new external_value(
447                             PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL),
448                         'plusfactor' => new external_value(
449                             PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL),
450                         'deleted' => new external_value(
451                             PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL),
452                         'hidden' => new external_value(
453                             PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL),
454                     ), 'Any grade item settings to alter', VALUE_DEFAULT, array()
455                 )
456             )
457         );
458     }
460     /**
461      * Update a grade item and, optionally, student grades
462      *
463      * @param  string $source       The source of the grade update
464      * @param  int $courseid        The course id
465      * @param  string $component    Component name
466      * @param  int $activityid      The activity id
467      * @param  int $itemnumber      The item number
468      * @param  array  $grades      Array of grades
469      * @param  array  $itemdetails Array of item details
470      * @return int                  A status flag
471      * @since Moodle 2.7
472      */
473     public static function update_grades($source, $courseid, $component, $activityid,
474         $itemnumber, $grades = array(), $itemdetails = array()) {
475         global $CFG;
477         $params = self::validate_parameters(
478             self::update_grades_parameters(),
479             array(
480                 'source' => $source,
481                 'courseid' => $courseid,
482                 'component' => $component,
483                 'activityid' => $activityid,
484                 'itemnumber' => $itemnumber,
485                 'grades' => $grades,
486                 'itemdetails' => $itemdetails
487             )
488         );
490         list($itemtype, $itemmodule) = normalize_component($params['component']);
492         if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
493             throw new moodle_exception('invalidcoursemodule');
494         }
495         $iteminstance = $cm->instance;
497         $coursecontext = context_course::instance($params['courseid']);
499         try {
500             self::validate_context($coursecontext);
501         } catch (Exception $e) {
502             $exceptionparam = new stdClass();
503             $exceptionparam->message = $e->getMessage();
504             $exceptionparam->courseid = $params['courseid'];
505             throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
506         }
508         $hidinggrades = false;
509         $editinggradeitem = false;
510         $editinggrades = false;
512         $gradestructure = array();
513         foreach ($grades as $grade) {
514             $editinggrades = true;
515             $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']);
516         }
517         if (!empty($params['itemdetails'])) {
518             if (isset($params['itemdetails']['hidden'])) {
519                 $hidinggrades = true;
520             } else {
521                 $editinggradeitem = true;
522             }
523         }
525         if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) {
526             throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
527                 'moodle/grade:manage required to edit grade information');
528         }
529         if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) &&
530             !has_capability('moodle/grade:hide', $coursecontext)) {
531             throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
532                 'moodle/grade:hide required to hide grade items');
533         }
534         if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) {
535             throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
536                 'moodle/grade:edit required to edit grades');
537         }
539         return grade_update($params['source'], $params['courseid'], $itemtype,
540             $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']);
541     }
543     /**
544      * Returns description of method result value
545      *
546      * @return external_description
547      * @since Moodle 2.7
548      */
549     public static function update_grades_returns() {
550         return new external_value(
551             PARAM_INT,
552             'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED
553             as defined in lib/grade/constants.php'
554         );
555     }