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