Файловый менеджер - Редактировать - /home/harasnat/www/mf/grade.tar
Назад
classes/external/get_groups_for_search_widget.php 0000604 00000013351 15062070555 0016454 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; use context_course; use core_external\external_api; use core_external\external_description; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; /** * External group report API implementation * * @package core_grades * @copyright 2022 Mathew May <mathew.solutions> * @category external * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated */ class get_groups_for_search_widget extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters * @deprecated since 4.2 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED), 'actionbaseurl' => new external_value(PARAM_URL, 'The base URL for the group action', VALUE_REQUIRED) ] ); } /** * Given a course ID find the existing user groups and map some fields to the returned array of group objects. * * @param int $courseid * @param string $actionbaseurl The base URL for the group action. * @return array Groups and warnings to pass back to the calling widget. * @deprecated since 4.2 */ public static function execute(int $courseid, string $actionbaseurl): array { global $DB, $USER, $COURSE; $params = self::validate_parameters( self::execute_parameters(), [ 'courseid' => $courseid, 'actionbaseurl' => $actionbaseurl ] ); $warnings = []; $context = context_course::instance($params['courseid']); parent::validate_context($context); $mappedgroups = []; $course = $DB->get_record('course', ['id' => $params['courseid']]); // Initialise the grade tracking object. if ($groupmode = $course->groupmode) { $aag = has_capability('moodle/site:accessallgroups', $context); $usergroups = []; $groupuserid = 0; if ($groupmode == VISIBLEGROUPS || $aag) { // Get user's own groups and put to the top. $usergroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid); } else { $groupuserid = $USER->id; } $allowedgroups = groups_get_all_groups($course->id, $groupuserid, $course->defaultgroupingid); $allgroups = array_merge($allowedgroups, $usergroups); // Filter out any duplicate groups. $groupsmenu = array_intersect_key($allgroups, array_unique(array_column($allgroups, 'name'))); if (!$allowedgroups || $groupmode == VISIBLEGROUPS || $aag) { array_unshift($groupsmenu, (object) [ 'id' => 0, 'name' => get_string('allparticipants'), ]); } $mappedgroups = array_map(function($group) use ($COURSE, $actionbaseurl, $context) { $url = new \moodle_url($actionbaseurl, [ 'id' => $COURSE->id, 'group' => $group->id ]); return (object) [ 'id' => $group->id, 'name' => format_string($group->name, true, ['context' => $context]), 'url' => $url->out(false), 'active' => false ]; }, $groupsmenu); } return [ 'groups' => $mappedgroups, 'warnings' => $warnings, ]; } /** * Returns description of what the group search for the widget should return. * * @return external_single_structure * @deprecated since 4.2 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'groups' => new external_multiple_structure(self::group_description()), 'warnings' => new external_warnings(), ]); } /** * Create group return value description. * * @return external_description */ public static function group_description(): external_description { $groupfields = [ 'id' => new external_value(PARAM_ALPHANUM, 'An ID for the group', VALUE_REQUIRED), 'url' => new external_value(PARAM_URL, 'The link that applies the group action', VALUE_REQUIRED), 'name' => new external_value(PARAM_TEXT, 'The full name of the group', VALUE_REQUIRED), 'active' => new external_value(PARAM_BOOL, 'Are we currently on this item?', VALUE_REQUIRED) ]; return new external_single_structure($groupfields); } /** * Mark the function as deprecated. * @return bool */ public static function execute_is_deprecated() { return true; } } classes/external/get_enrolled_users_for_search_widget.php 0000604 00000014413 15062070555 0020162 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; use core_external\external_api; use core_external\external_description; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use core_external\restricted_context_exception; use moodle_url; use core_user; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/grade/lib.php'); /** * Get the enrolled users within and map some fields to the returned array of user objects. * * @package core_grades * @copyright 2022 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 4.1 * @deprecated */ class get_enrolled_users_for_search_widget extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters * @deprecated since 4.2 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED), 'actionbaseurl' => new external_value(PARAM_URL, 'The base URL for the user option', VALUE_REQUIRED), 'groupid' => new external_value(PARAM_INT, 'Group Id', VALUE_DEFAULT, 0) ] ); } /** * Given a course ID find the enrolled users within and map some fields to the returned array of user objects. * * @param int $courseid * @param string $actionbaseurl The base URL for the user option. * @param int|null $groupid * @return array Users and warnings to pass back to the calling widget. * @throws coding_exception * @throws invalid_parameter_exception * @throws moodle_exception * @throws restricted_context_exception * @deprecated since 4.2 */ public static function execute(int $courseid, string $actionbaseurl, ?int $groupid = 0): array { global $DB, $PAGE; $params = self::validate_parameters( self::execute_parameters(), [ 'courseid' => $courseid, 'actionbaseurl' => $actionbaseurl, 'groupid' => $groupid ] ); $warnings = []; $coursecontext = \context_course::instance($params['courseid']); parent::validate_context($coursecontext); require_capability('moodle/course:viewparticipants', $coursecontext); $course = $DB->get_record('course', ['id' => $params['courseid']]); // Create a graded_users_iterator because it will properly check the groups etc. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); $gui = new \graded_users_iterator($course, null, $params['groupid']); $gui->require_active_enrolment($showonlyactiveenrol); $gui->init(); $users = []; while ($userdata = $gui->next_user()) { $guiuser = $userdata->user; $user = new \stdClass(); $user->fullname = fullname($guiuser); $user->id = $guiuser->id; $user->url = (new moodle_url($actionbaseurl, ['id' => $courseid, 'userid' => $guiuser->id]))->out(false); $userpicture = new \user_picture($guiuser); $userpicture->size = 1; $user->profileimage = $userpicture->get_url($PAGE)->out(false); $user->email = $guiuser->email; $user->active = false; $users[] = $user; } $gui->close(); return [ 'users' => $users, 'warnings' => $warnings, ]; } /** * Returns description of method result value. * * @return external_single_structure * @deprecated since 4.2 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'users' => new external_multiple_structure(self::user_description()), 'warnings' => new external_warnings(), ]); } /** * Create user return value description. * * @return external_description */ public static function user_description(): external_description { $userfields = [ 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'), 'profileimage' => new external_value( PARAM_URL, 'The location of the users larger image', VALUE_OPTIONAL ), 'url' => new external_value( PARAM_URL, 'The link to the user report', VALUE_OPTIONAL ), 'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL), 'email' => new external_value( core_user::get_property_type('email'), 'An email address - allow email as root@localhost', VALUE_OPTIONAL), 'active' => new external_value(PARAM_BOOL, 'Are we currently on this item?', VALUE_REQUIRED) ]; return new external_single_structure($userfields); } /** * Mark the function as deprecated. * @return bool */ public static function execute_is_deprecated() { return true; } } classes/external/create_gradecategories.php 0000604 00000027106 15062070555 0015216 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; defined('MOODLE_INTERNAL') || die; require_once("$CFG->libdir/gradelib.php"); require_once("$CFG->dirroot/grade/edit/tree/lib.php"); /** * Create gradecategories webservice. * * @package core_grades * @copyright 2021 Peter Burnett <peterburnett@catalyst-au.net> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.11 */ class create_gradecategories extends external_api { /** * Returns description of method parameters * * @return external_function_parameters * @since Moodle 3.11 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters( [ 'courseid' => new external_value(PARAM_INT, 'id of course', VALUE_REQUIRED), 'categories' => new external_multiple_structure( new external_single_structure([ 'fullname' => new external_value(PARAM_TEXT, 'fullname of category', VALUE_REQUIRED), 'options' => new external_single_structure([ 'aggregation' => new external_value(PARAM_INT, 'aggregation method', VALUE_OPTIONAL), 'aggregateonlygraded' => new external_value(PARAM_BOOL, 'exclude empty grades', VALUE_OPTIONAL), 'aggregateoutcomes' => new external_value(PARAM_BOOL, 'aggregate outcomes', VALUE_OPTIONAL), 'droplow' => new external_value(PARAM_INT, 'drop low grades', VALUE_OPTIONAL), 'itemname' => new external_value(PARAM_TEXT, 'the category total name', VALUE_OPTIONAL), 'iteminfo' => new external_value(PARAM_TEXT, 'the category iteminfo', VALUE_OPTIONAL), 'idnumber' => new external_value(PARAM_TEXT, 'the category idnumber', VALUE_OPTIONAL), 'gradetype' => new external_value(PARAM_INT, 'the grade type', VALUE_OPTIONAL), 'grademax' => new external_value(PARAM_INT, 'the grade max', VALUE_OPTIONAL), 'grademin' => new external_value(PARAM_INT, 'the grade min', VALUE_OPTIONAL), 'gradepass' => new external_value(PARAM_INT, 'the grade to pass', VALUE_OPTIONAL), 'display' => new external_value(PARAM_INT, 'the display type', VALUE_OPTIONAL), 'decimals' => new external_value(PARAM_INT, 'the decimal count', VALUE_OPTIONAL), 'hiddenuntil' => new external_value(PARAM_INT, 'grades hidden until', VALUE_OPTIONAL), 'locktime' => new external_value(PARAM_INT, 'lock grades after', VALUE_OPTIONAL), 'weightoverride' => new external_value(PARAM_BOOL, 'weight adjusted', VALUE_OPTIONAL), 'aggregationcoef2' => new external_value(PARAM_RAW, 'weight coefficient', VALUE_OPTIONAL), 'parentcategoryid' => new external_value(PARAM_INT, 'The parent category id', VALUE_OPTIONAL), 'parentcategoryidnumber' => new external_value(PARAM_TEXT, 'the parent category idnumber', VALUE_OPTIONAL), ], 'optional category data', VALUE_DEFAULT, []), ], 'Category to create', VALUE_REQUIRED) , 'Categories to create', VALUE_REQUIRED) ] ); } /** * Creates gradecategories inside of the specified course. * * @param int $courseid the courseid to create the gradecategory in. * @param array $categories the categories to create. * @return array array of created categoryids and warnings. * @since Moodle 3.11 */ public static function execute(int $courseid, array $categories): array { $params = self::validate_parameters(self::execute_parameters(), ['courseid' => $courseid, 'categories' => $categories]); // Now params are validated, update the references. $courseid = $params['courseid']; $categories = $params['categories']; // Check that the context and permissions are OK. $context = \context_course::instance($courseid); self::validate_context($context); require_capability('moodle/grade:manage', $context); return self::create_gradecategories_from_data($courseid, $categories); } /** * Returns description of method result value * * @return external_single_structure * @since Moodle 3.11 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'categoryids' => new external_multiple_structure( new external_value(PARAM_INT, 'created cateogry ID') ), 'warnings' => new external_warnings(), ]); } /** * Takes an array of categories and creates the inside the category tree for the supplied courseid. * * @param int $courseid the courseid to create the categories inside of. * @param array $categories the categories to create. * @return array array of results and warnings. */ public static function create_gradecategories_from_data(int $courseid, array $categories): array { global $CFG, $DB; $defaultparentcat = \grade_category::fetch_course_category($courseid); // Setup default data so WS call needs to contain only data to set. // This is not done in the Parameters, so that the array of options can be optional. $defaultdata = [ 'aggregation' => grade_get_setting($courseid, 'aggregation', $CFG->grade_aggregation, true), 'aggregateonlygraded' => 1, 'aggregateoutcomes' => 0, 'droplow' => 0, 'grade_item_itemname' => '', 'grade_item_iteminfo' => '', 'grade_item_idnumber' => '', 'grade_item_gradetype' => GRADE_TYPE_VALUE, 'grade_item_grademax' => 100, 'grade_item_grademin' => 1, 'grade_item_gradepass' => 1, 'grade_item_display' => GRADE_DISPLAY_TYPE_DEFAULT, // Hack. This must be -2 to use the default setting. 'grade_item_decimals' => -2, 'grade_item_hiddenuntil' => 0, 'grade_item_locktime' => 0, 'grade_item_weightoverride' => 0, 'grade_item_aggregationcoef2' => 0, 'parentcategory' => $defaultparentcat->id ]; // Most of the data items need boilerplate prepended. These are the exceptions. $ignorekeys = [ 'aggregation', 'aggregateonlygraded', 'aggregateoutcomes', 'droplow', 'parentcategoryid', 'parentcategoryidnumber' ]; $createdcats = []; foreach ($categories as $category) { // Setup default data so WS call needs to contain only data to set. // This is not done in the Parameters, so that the array of options can be optional. $data = $defaultdata; $data['fullname'] = $category['fullname']; foreach ($category['options'] as $key => $value) { if (!in_array($key, $ignorekeys)) { $fullkey = 'grade_item_' . $key; $data[$fullkey] = $value; } else { $data[$key] = $value; } } // Handle parent category special case. // This figures the parent category id from the provided id OR idnumber. if (array_key_exists('parentcategoryid', $category['options']) && $parentcat = $DB->get_record('grade_categories', ['id' => $category['options']['parentcategoryid'], 'courseid' => $courseid])) { $data['parentcategory'] = $parentcat->id; } else if (array_key_exists('parentcategoryidnumber', $category['options']) && $parentcatgradeitem = $DB->get_record('grade_items', [ 'itemtype' => 'category', 'courseid' => $courseid, 'idnumber' => $category['options']['parentcategoryidnumber'] ], '*', IGNORE_MULTIPLE)) { if ($parentcat = $DB->get_record('grade_categories', ['courseid' => $courseid, 'id' => $parentcatgradeitem->iteminstance])) { $data['parentcategory'] = $parentcat->id; } } // Create new gradecategory item. $gradecategory = new \grade_category(['courseid' => $courseid], false); $gradecategory->apply_default_settings(); $gradecategory->apply_forced_settings(); // Data Validation. if (array_key_exists('grade_item_gradetype', $data) and $data['grade_item_gradetype'] == GRADE_TYPE_SCALE) { if (empty($data['grade_item_scaleid'])) { $warnings[] = ['item' => 'scaleid', 'warningcode' => 'invalidscale', 'message' => get_string('missingscale', 'grades')]; } } if (array_key_exists('grade_item_grademin', $data) and array_key_exists('grade_item_grademax', $data)) { if (($data['grade_item_grademax'] != 0 OR $data['grade_item_grademin'] != 0) AND ($data['grade_item_grademax'] == $data['grade_item_grademin'] OR $data['grade_item_grademax'] < $data['grade_item_grademin'])) { $warnings[] = ['item' => 'grademax', 'warningcode' => 'invalidgrade', 'message' => get_string('incorrectminmax', 'grades')]; } } if (!empty($warnings)) { return ['categoryids' => [], 'warnings' => $warnings]; } // Now call the update function with data. Transactioned so the gradebook isn't broken on bad data. // This is done per-category so that children can correctly discover the parent categories. try { $transaction = $DB->start_delegated_transaction(); \grade_edit_tree::update_gradecategory($gradecategory, (object) $data); $transaction->allow_commit(); $createdcats[] = $gradecategory->id; } catch (\Exception $e) { // If the submitted data was broken for any reason. $warnings['database'] = $e->getMessage(); $transaction->rollback($e); return ['warnings' => $warnings]; } } return['categoryids' => $createdcats, 'warnings' => []]; } } classes/external/get_grade_tree.php 0000604 00000010672 15062070555 0013503 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_value; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/grade/lib.php'); /** * Web service to return the grade tree structure for a given course. * * @package core_grades * @copyright 2023 Mihail Geshoski <mihail@moodle.com> * @category external * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_grade_tree extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED) ] ); } /** * Given a course ID, return the grade tree structure for that course. * * @param int $courseid The course ID. * @return string JSON encoded data representing the course grade tree structure. */ public static function execute(int $courseid): string { $params = self::validate_parameters( self::execute_parameters(), [ 'courseid' => $courseid ] ); $context = \context_course::instance($params['courseid']); parent::validate_context($context); // Make sure that the user has the capability to view the full grade tree in the course. require_capability('moodle/grade:viewall', $context); // Get the course grade category. $coursegradecategory = \grade_category::fetch_course_category($params['courseid']); $coursegradetree = self::generate_course_grade_tree($coursegradecategory); return json_encode($coursegradetree); } /** * Describes the return structure. * * @return external_value */ public static function execute_returns(): external_value { return new external_value(PARAM_RAW, 'JSON encoded data representing the course grade tree structure.'); } /** * Recursively generates the course grade tree structure. * * @param \grade_category $gradecategory The course grade category. * @return array The course grade tree structure. */ private static function generate_course_grade_tree(\grade_category $gradecategory): array { $gradecategorydata = [ 'id' => $gradecategory->id, 'name' => $gradecategory->get_name(), 'iscategory' => true, 'haschildcategories' => false ]; // Get the children of the grade category. if ($gradecategorychildren = $gradecategory->get_children()) { foreach ($gradecategorychildren as $child) { // If the child is a grade category, recursively generate the grade tree structure for that category. if ($child['object'] instanceof \grade_category) { $gradecategorydata['haschildcategories'] = true; $gradecategorydata['children'][] = self::generate_course_grade_tree($child['object']); } else { // Otherwise, add the grade item to the grade tree structure. $gradecategorydata['children'][] = [ 'id' => $child['object']->id, 'name' => $child['object']->get_name(), 'iscategory' => false, 'children' => null ]; } } } else { // If the grade category has no children, set the children property to null. $gradecategorydata['children'] = null; } return $gradecategorydata; } } classes/external/get_feedback.php 0000604 00000010236 15062070555 0013122 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use invalid_parameter_exception; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/grade/lib.php'); /** * Web service to fetch students feedback for a grade item. * * @package core_grades * @copyright 2023 Kevin Percy <kevin.percy@moodle.com> * @category external * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_feedback extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED), 'userid' => new external_value(PARAM_INT, 'User ID', VALUE_REQUIRED), 'itemid' => new external_value(PARAM_INT, 'Grade Item ID', VALUE_REQUIRED) ] ); } /** * Given a user ID and grade item ID, return feedback and user details. * * @param int $courseid The course ID. * @param int $userid * @param int $itemid * @return array Feedback and user details */ public static function execute(int $courseid, int $userid, int $itemid): array { global $OUTPUT, $CFG; $params = self::validate_parameters( self::execute_parameters(), [ 'courseid' => $courseid, 'userid' => $userid, 'itemid' => $itemid ] ); $context = \context_course::instance($courseid); parent::validate_context($context); require_capability('gradereport/grader:view', $context); $gtree = new \grade_tree($params['courseid'], false, false, null, !$CFG->enableoutcomes); $gradeitem = $gtree->get_item($params['itemid']); // If Item ID is not part of Course ID, $gradeitem will be set to false. if ($gradeitem === false) { throw new invalid_parameter_exception('Course ID and item ID mismatch'); } $grade = $gradeitem->get_grade($params['userid'], false); $user = \core_user::get_user($params['userid']); $extrafields = \core_user\fields::get_identity_fields($context); return [ 'feedbacktext' => $grade->feedback, 'title' => $gradeitem->get_name(true), 'fullname' => fullname($user), 'picture' => $OUTPUT->user_picture($user, ['size' => 50, 'link' => false]), 'additionalfield' => empty($extrafields) ? '' : $user->{$extrafields[0]}, ]; } /** * Describes the return structure. * * @return external_single_structure */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'feedbacktext' => new external_value(PARAM_RAW, 'The full feedback text'), 'title' => new external_value(PARAM_TEXT, 'Title of the grade item that the feedback is for'), 'fullname' => new external_value(PARAM_TEXT, 'Students name'), 'picture' => new external_value(PARAM_RAW, 'Students picture'), 'additionalfield' => new external_value(PARAM_RAW, 'Additional field for the user (email or ID number, for example)'), ]); } } classes/external/get_gradeitems.php 0000604 00000007220 15062070555 0013521 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; defined('MOODLE_INTERNAL') || die; use context_course; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use core_external\restricted_context_exception; use grade_item; require_once($CFG->libdir . '/gradelib.php'); /** * External grade get gradeitems API implementation * * @package core_grades * @copyright 2023 Mathew May <mathew.solutions> * @category external * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_gradeitems extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED) ] ); } /** * Given a course ID find the grading objects and return their names & IDs. * * @param int $courseid * @return array * @throws restricted_context_exception * @throws \invalid_parameter_exception */ public static function execute(int $courseid): array { $params = self::validate_parameters( self::execute_parameters(), [ 'courseid' => $courseid ] ); $warnings = []; $context = context_course::instance($params['courseid']); parent::validate_context($context); $allgradeitems = grade_item::fetch_all(['courseid' => $params['courseid']]); $gradeitems = array_filter($allgradeitems, function($item) { $item->itemname = $item->get_name(); $item->category = $item->get_parent_category()->get_name(); return $item->gradetype != GRADE_TYPE_NONE && !$item->is_category_item() && !$item->is_course_item(); }); return [ 'gradeItems' => $gradeitems, 'warnings' => $warnings, ]; } /** * Returns description of what gradeitems fetch should return. * * @return external_single_structure */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'gradeItems' => new external_multiple_structure( new external_single_structure([ 'id' => new external_value(PARAM_ALPHANUM, 'An ID for the grade item', VALUE_REQUIRED), 'itemname' => new external_value(PARAM_CLEANHTML, 'The full name of the grade item', VALUE_REQUIRED), 'category' => new external_value(PARAM_TEXT, 'The grade category of the grade item', VALUE_OPTIONAL), ]) ), 'warnings' => new external_warnings(), ]); } } classes/external/get_enrolled_users_for_selector.php 0000604 00000011060 15062070555 0017165 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\external; use core_user_external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use core_external\restricted_context_exception; use user_picture; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/grade/lib.php'); require_once($CFG->dirroot .'/user/externallib.php'); /** * Get the enrolled users within and map some fields to the returned array of user objects. * * @package core_grades * @copyright 2022 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 4.2 */ class get_enrolled_users_for_selector extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ( [ 'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED), 'groupid' => new external_value(PARAM_INT, 'Group Id', VALUE_DEFAULT, 0) ] ); } /** * Given a course ID find the enrolled users within and map some fields to the returned array of user objects. * * @param int $courseid * @param int|null $groupid * @return array Users and warnings to pass back to the calling widget. * @throws coding_exception * @throws invalid_parameter_exception * @throws moodle_exception * @throws restricted_context_exception */ public static function execute(int $courseid, ?int $groupid = 0): array { global $DB, $PAGE; $params = self::validate_parameters( self::execute_parameters(), [ 'courseid' => $courseid, 'groupid' => $groupid ] ); $warnings = []; $coursecontext = \context_course::instance($params['courseid']); parent::validate_context($coursecontext); require_capability('moodle/course:viewparticipants', $coursecontext); $course = $DB->get_record('course', ['id' => $params['courseid']]); // Create a graded_users_iterator because it will properly check the groups etc. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); $gui = new \graded_users_iterator($course, null, $params['groupid']); $gui->require_active_enrolment($showonlyactiveenrol); $gui->init(); $users = []; while ($userdata = $gui->next_user()) { $user = $userdata->user; $user->fullname = fullname($user); $userpicture = new user_picture($user); $userpicture->size = 1; $user->profileimageurl = $userpicture->get_url($PAGE)->out(false); $userpicture->size = 0; // Size f2. $user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false); $users[] = $user; } $gui->close(); return [ 'users' => $users, 'warnings' => $warnings, ]; } /** * Returns description of method result value. * * @return external_single_structure */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'users' => new external_multiple_structure(core_user_external::user_description()), 'warnings' => new external_warnings(), ]); } } classes/output/import_key_manager_action_bar.php 0000604 00000004476 15062070555 0016323 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook import key manager page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class import_key_manager_action_bar extends action_bar { /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/import_key_manager_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector and imports navigation selector. $importnavselectors = new import_action_bar($this->context, null, 'keymanager'); $data = $importnavselectors->export_for_template($output); // Add a button to the action bar with a link to the 'add user key' page. $adduserkeylink = new moodle_url('/grade/import/key.php', ['courseid' => $courseid]); $adduserkeybutton = new \single_button($adduserkeylink, get_string('adduserkey', 'userkey'), 'get', \single_button::BUTTON_PRIMARY); $data['adduserkeybutton'] = $adduserkeybutton->export_for_template($output); return $data; } } classes/output/manage_outcomes_action_bar.php 0000604 00000007716 15062070555 0015615 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the manage outcomes page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class manage_outcomes_action_bar extends action_bar { /** @var bool $hasoutcomes Whether there are existing outcomes. */ protected $hasoutcomes; /** * The class constructor. * * @param \context $context The context object. * @param bool $hasoutcomes Whether there are existing outcomes. */ public function __construct(\context $context, bool $hasoutcomes) { parent::__construct($context); $this->hasoutcomes = $hasoutcomes; } /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/manage_outcomes_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { $data = []; $courseid = 0; // Display the following buttons only if the user is in course gradebook. if ($this->context->contextlevel === CONTEXT_COURSE) { $courseid = $this->context->instanceid; // Add a button to the action bar with a link to the 'course outcomes' page. $backlink = new moodle_url('/grade/edit/outcome/course.php', ['id' => $courseid]); $backbutton = new \single_button($backlink, get_string('back'), 'get'); $data['backbutton'] = $backbutton->export_for_template($output); // Add a button to the action bar with a link to the 'import outcomes' page. The import outcomes // functionality is currently only available in the course context. $importoutcomeslink = new moodle_url('/grade/edit/outcome/import.php', ['courseid' => $courseid]); $importoutcomesbutton = new \single_button($importoutcomeslink, get_string('importoutcomes', 'grades'), 'get'); $data['importoutcomesbutton'] = $importoutcomesbutton->export_for_template($output); } // Add a button to the action bar with a link to the 'add new outcome' page. $addoutcomelink = new moodle_url('/grade/edit/outcome/edit.php', ['courseid' => $courseid]); $addoutcomebutton = new \single_button($addoutcomelink, get_string('outcomecreate', 'grades'), 'get', \single_button::BUTTON_PRIMARY); $data['addoutcomebutton'] = $addoutcomebutton->export_for_template($output); if ($this->hasoutcomes) { // Add a button to the action bar which enables export of all existing outcomes. $exportoutcomeslink = new moodle_url('/grade/edit/outcome/export.php', ['id' => $courseid, 'sesskey' => sesskey()]); $exportoutcomesbutton = new \single_button($exportoutcomeslink, get_string('exportalloutcomes', 'grades'), 'get'); $data['exportoutcomesbutton'] = $exportoutcomesbutton->export_for_template($output); } return $data; } } classes/output/action_bar.php 0000604 00000002702 15062070555 0012355 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use templatable; use renderable; /** * The base class for the action bar in the gradebook pages. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class action_bar implements templatable, renderable { /** @var \context $context The context object. */ protected $context; /** * The class constructor. * * @param \context $context The context object. */ public function __construct(\context $context) { $this->context = $context; } /** * Returns the template for the actions bar. * * @return string */ abstract public function get_template(): string; } classes/output/export_publish_action_bar.php 0000604 00000004667 15062070555 0015520 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook publish export page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class export_publish_action_bar extends action_bar { /** @var string $activeplugin The plugin of the current export grades page (xml, ods, ...). */ protected $activeplugin; /** * The class constructor. * * @param \context $context The context object. * @param string $activeplugin The plugin of the current export grades page (xml, ods, ...). */ public function __construct(\context $context, string $activeplugin) { parent::__construct($context); $this->activeplugin = $activeplugin; } /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/export_publish_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Add a back button to the action bar. $backlink = new moodle_url("/grade/export/{$this->activeplugin}/index.php", ['id' => $courseid]); $backbutton = new \single_button($backlink, get_string('back'), 'get'); return [ 'backbutton' => $backbutton->export_for_template($output) ]; } } classes/output/general_action_bar.php 0000604 00000017260 15062070555 0014057 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; use core\output\select_menu; /** * Renderable class for the general action bar in the gradebook pages. * * This class is responsible for rendering the general navigation select menu in the gradebook pages. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class general_action_bar extends action_bar { /** @var moodle_url $activeurl The URL that should be set as active in the URL selector element. */ protected $activeurl; /** * The type of the current gradebook page (report, settings, import, export, scales, outcomes, letters). * * @var string $activetype */ protected $activetype; /** @var string $activeplugin The plugin of the current gradebook page (grader, fullview, ...). */ protected $activeplugin; /** * The class constructor. * * @param \context $context The context object. * @param moodle_url $activeurl The URL that should be set as active in the URL selector element. * @param string $activetype The type of the current gradebook page (report, settings, import, export, scales, * outcomes, letters). * @param string $activeplugin The plugin of the current gradebook page (grader, fullview, ...). */ public function __construct(\context $context, moodle_url $activeurl, string $activetype, string $activeplugin) { parent::__construct($context); $this->activeurl = $activeurl; $this->activetype = $activetype; $this->activeplugin = $activeplugin; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { $selectmenu = $this->get_action_selector(); if (is_null($selectmenu)) { return []; } return [ 'generalnavselector' => $selectmenu->export_for_template($output), ]; } /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/general_action_bar'; } /** * Returns the URL selector object. * * @return \select_menu|null The URL select object. */ private function get_action_selector(): ?select_menu { if ($this->context->contextlevel !== CONTEXT_COURSE) { return null; } $courseid = $this->context->instanceid; $plugininfo = grade_get_plugin_info($courseid, $this->activetype, $this->activeplugin); $menu = []; $viewgroup = []; $setupgroup = []; $moregroup = []; foreach ($plugininfo as $plugintype => $plugins) { // Skip if the plugintype value is 'strings'. This particular item only returns an array of strings // which we do not need. if ($plugintype == 'strings') { continue; } // If $plugins is actually the definition of a child-less parent link. if (!empty($plugins->id)) { $string = $plugins->string; if (!empty($plugininfo[$this->activetype]->parent)) { $string = $plugininfo[$this->activetype]->parent->string; } $menu[$plugins->link->out(false)] = $string; continue; } foreach ($plugins as $key => $plugin) { // Depending on the plugin type, include the plugin to the appropriate item group for the URL selector // element. switch ($plugintype) { case 'report': $viewgroup[$plugin->link->out(false)] = $plugin->string; break; case 'settings': $setupgroup[$plugin->link->out(false)] = $plugin->string; break; case 'scale': // We only need the link to the 'view scales' page, otherwise skip and continue to the next // plugin. if ($key !== 'view') { continue 2; } $moregroup[$plugin->link->out(false)] = get_string('scales'); break; case 'outcome': // We only need the link to the 'outcomes used in course' page, otherwise skip and continue to // the next plugin. if ($key !== 'course') { continue 2; } $moregroup[$plugin->link->out(false)] = get_string('outcomes', 'grades'); break; case 'letter': // We only need the link to the 'view grade letters' page, otherwise skip and continue to the // next plugin. if ($key !== 'view') { continue 2; } $moregroup[$plugin->link->out(false)] = get_string('gradeletters', 'grades'); break; case 'import': $link = new moodle_url('/grade/import/index.php', ['id' => $courseid]); // If the link to the grade import options is already added to the group, skip and continue to // the next plugin. if (array_key_exists($link->out(false), $moregroup)) { continue 2; } $moregroup[$link->out(false)] = get_string('import', 'grades'); break; case 'export': $link = new moodle_url('/grade/export/index.php', ['id' => $courseid]); // If the link to the grade export options is already added to the group, skip and continue to // the next plugin. if (array_key_exists($link->out(false), $moregroup)) { continue 2; } $moregroup[$link->out(false)] = get_string('export', 'grades'); break; } } } if (!empty($viewgroup)) { $menu[][get_string('view')] = $viewgroup; } if (!empty($setupgroup)) { $menu[][get_string('setup', 'grades')] = $setupgroup; } if (!empty($moregroup)) { $menu[][get_string('moremenu')] = $moregroup; } $selectmenu = new select_menu('gradesactionselect', $menu, $this->activeurl->out(false)); $selectmenu->set_label(get_string('gradebooknavigationmenu', 'grades'), ['class' => 'sr-only']); return $selectmenu; } } classes/output/export_key_manager_action_bar.php 0000604 00000004477 15062070555 0016333 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook exports key manager page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class export_key_manager_action_bar extends action_bar { /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/export_key_manager_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector and exports navigation selector. $exportnavselectors = new export_action_bar($this->context, null, 'keymanager'); $data = $exportnavselectors->export_for_template($output); // Add a button to the action bar with a link to the 'add user key' page. $adduserkeylink = new moodle_url('/grade/export/key.php', ['courseid' => $courseid]); $adduserkeybutton = new \single_button($adduserkeylink, get_string('adduserkey', 'userkey'), 'get', \single_button::BUTTON_PRIMARY); $data['adduserkeybutton'] = $adduserkeybutton->export_for_template($output); return $data; } } classes/output/export_action_bar.php 0000604 00000007764 15062070555 0013773 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook export pages. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class export_action_bar extends action_bar { /** @var string $activeplugin The plugin of the current export grades page (xml, ods, ...). */ protected $activeplugin; /** * The class constructor. * * @param \context $context The context object. * @param null $unused This parameter has been deprecated since 4.1 and should not be used anymore. * @param string $activeplugin The plugin of the current export grades page (xml, ods, ...). */ public function __construct(\context $context, $unused, string $activeplugin) { if ($unused !== null) { debugging('Deprecated argument passed to ' . __FUNCTION__, DEBUG_DEVELOPER); } parent::__construct($context); $this->activeplugin = $activeplugin; } /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/export_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector. $generalnavselector = new general_action_bar($this->context, new moodle_url('/grade/export/index.php', ['id' => $courseid]), 'export', $this->activeplugin); $data = $generalnavselector->export_for_template($output); // Get all grades export plugins. If there isn't any available export plugins there is no need to create and // display the exports navigation selector menu. Therefore, return only the current data. if (!$exports = \grade_helper::get_plugins_export($courseid)) { return $data; } // If exports key management is enabled, always display this item at the end of the list. if (array_key_exists('keymanager', $exports)) { $keymanager = $exports['keymanager']; unset($exports['keymanager']); $exports['keymanager'] = $keymanager; } $exportsmenu = []; $exportactiveurl = null; // Generate the data for the exports navigation selector menu. foreach ($exports as $export) { $exportsmenu[$export->link->out()] = $export->string; if ($export->id == $this->activeplugin) { $exportactiveurl = $export->link->out(); } } // This navigation selector menu will contain the links to all available grade export plugin pages. $exportsurlselect = new \core\output\select_menu('exportas', $exportsmenu, $exportactiveurl); $exportsurlselect->set_label(get_string('exportas', 'grades')); $data['exportselector'] = $exportsurlselect->export_for_template($output); return $data; } } classes/output/scales_action_bar.php 0000604 00000004735 15062070555 0013717 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook scales page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class scales_action_bar extends action_bar { /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/scales_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { $data = []; $courseid = 0; // If in the course context, we should display the general navigation selector in gradebook. if ($this->context->contextlevel === CONTEXT_COURSE) { $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector. $generalnavselector = new general_action_bar($this->context, new moodle_url('/grade/edit/scale/index.php', ['id' => $courseid]), 'scale', 'scale'); $data = $generalnavselector->export_for_template($output); } // Add a button to the action bar with a link to the 'add new scale' page. $addnewscalelink = new moodle_url('/grade/edit/scale/edit.php', ['courseid' => $courseid]); $addnewscalebutton = new \single_button($addnewscalelink, get_string('scalescustomcreate'), 'get', \single_button::BUTTON_PRIMARY); $data['addnewscalebutton'] = $addnewscalebutton->export_for_template($output); return $data; } } classes/output/course_outcomes_action_bar.php 0000604 00000004761 15062070555 0015662 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook course outcomes page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_outcomes_action_bar extends action_bar { /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/course_outcomes_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector. $generalnavselector = new general_action_bar($this->context, new moodle_url('/grade/edit/outcome/course.php', ['id' => $courseid]), 'outcome', 'course'); $data = $generalnavselector->export_for_template($output); if (has_capability('moodle/grade:manageoutcomes', $this->context)) { // Add a button to the action bar with a link to the 'manage outcomes' page. $manageoutcomeslink = new moodle_url('/grade/edit/outcome/index.php', ['id' => $courseid]); $manageoutcomesbutton = new \single_button($manageoutcomeslink, get_string('manageoutcomes', 'grades'), 'get', \single_button::BUTTON_PRIMARY); $data['manageoutcomesbutton'] = $manageoutcomesbutton->export_for_template($output); } return $data; } } classes/output/grade_letters_action_bar.php 0000604 00000004615 15062070555 0015266 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the grade letters page. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_letters_action_bar extends action_bar { /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/grade_letters_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { $data = []; // If in the course context, we should display the general navigation selector in gradebook. if ($this->context->contextlevel === CONTEXT_COURSE) { // Get the data used to output the general navigation selector. $generalnavselector = new general_action_bar($this->context, new moodle_url('/grade/edit/letter/index.php', ['id' => $this->context->id]), 'letter', 'view'); $data = $generalnavselector->export_for_template($output); } // Add a button to the action bar with a link to the 'edit grade letters' page. $editbuttonlink = new moodle_url('/grade/edit/letter/index.php', ['id' => $this->context->id, 'edit' => 1]); $editbutton = new \single_button($editbuttonlink, get_string('edit'), 'get', \single_button::BUTTON_PRIMARY); $data['editbutton'] = $editbutton->export_for_template($output); return $data; } } classes/output/import_action_bar.php 0000604 00000007764 15062070555 0013764 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; /** * Renderable class for the action bar elements in the gradebook import pages. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class import_action_bar extends action_bar { /** @var string $activeplugin The plugin of the current import grades page (xml, csv, ...). */ protected $activeplugin; /** * The class constructor. * * @param \context $context The context object. * @param null $unused This parameter has been deprecated since 4.1 and should not be used anymore. * @param string $activeplugin The plugin of the current import grades page (xml, csv, ...). */ public function __construct(\context $context, $unused, string $activeplugin) { if ($unused !== null) { debugging('Deprecated argument passed to ' . __FUNCTION__, DEBUG_DEVELOPER); } parent::__construct($context); $this->activeplugin = $activeplugin; } /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/import_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector. $generalnavselector = new general_action_bar($this->context, new moodle_url('/grade/import/index.php', ['id' => $courseid]), 'import', $this->activeplugin); $data = $generalnavselector->export_for_template($output); // Get all grades import plugins. If there isn't any available import plugins there is no need to create and // display the imports navigation selector menu. Therefore, return only the current data. if (!$imports = \grade_helper::get_plugins_import($courseid)) { return $data; } // If imports key management is enabled, always display this item at the end of the list. if (array_key_exists('keymanager', $imports)) { $keymanager = $imports['keymanager']; unset($imports['keymanager']); $imports['keymanager'] = $keymanager; } $importsmenu = []; $importactiveurl = null; // Generate the data for the imports navigation selector menu. foreach ($imports as $import) { $importsmenu[$import->link->out()] = $import->string; if ($import->id == $this->activeplugin) { $importactiveurl = $import->link->out(); } } // This navigation selector menu will contain the links to all available grade export plugin pages. $importsurlselect = new \core\output\select_menu('importas', $importsmenu, $importactiveurl); $importsurlselect->set_label(get_string('importas', 'grades')); $data['importselector'] = $importsurlselect->export_for_template($output); return $data; } } classes/output/gradebook_setup_action_bar.php 0000604 00000007553 15062070555 0015623 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\output; use moodle_url; use html_writer; /** * Renderable class for the action bar elements in the gradebook setup pages. * * @package core_grades * @copyright 2021 Mihail Geshoski <mihail@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class gradebook_setup_action_bar extends action_bar { /** * Returns the template for the action bar. * * @return string */ public function get_template(): string { return 'core_grades/gradebook_setup_action_bar'; } /** * Export the data for the mustache template. * * @param \renderer_base $output renderer to be used to render the action bar elements. * @return array */ public function export_for_template(\renderer_base $output): array { global $CFG; if ($this->context->contextlevel !== CONTEXT_COURSE) { return []; } $courseid = $this->context->instanceid; // Get the data used to output the general navigation selector. $generalnavselector = new general_action_bar($this->context, new moodle_url('/grade/edit/tree/index.php', ['id' => $courseid]), 'settings', 'setup'); $data = $generalnavselector->export_for_template($output); $actions = []; $additemurl = new moodle_url('#'); // Add a button to the action bar dropdown with a link to the 'add grade item' modal. $actions[] = new \action_menu_link_secondary( $additemurl, null, get_string('additem', 'grades'), [ 'data-courseid' => $courseid, 'data-itemid' => -1, 'data-trigger' => 'add-item-form', 'data-gprplugin' => 'tree', ] ); // If outcomes are enabled, add a button to the action bar dropdown with a link to the 'add outcome item' modal. if (!empty($CFG->enableoutcomes) && count(\grade_outcome::fetch_all_available($courseid)) > 0) { // Add a button to the action bar dropdown with a link to the 'add outcome item' modal. $actions[] = new \action_menu_link_secondary( $additemurl, null, get_string('addoutcomeitem', 'grades'), [ 'data-courseid' => $courseid, 'data-itemid' => -1, 'data-trigger' => 'add-outcome-form', 'data-gprplugin' => 'tree', ] ); } // Add a button to the action bar dropdown with a link to the 'add category' modal. $actions[] = new \action_menu_link_secondary( $additemurl, null, get_string('addcategory', 'grades'), [ 'data-courseid' => $courseid, 'data-category' => -1, 'data-trigger' => 'add-category-form', 'data-gprplugin' => 'tree', ] ); $addmenu = new \action_menu($actions); $addmenu->set_menu_trigger(get_string('add'), 'btn font-weight-bold'); $data['addmenu'] = $addmenu->export_for_template($output); return $data; } } classes/form/add_item.php 0000604 00000057554 15062070555 0011444 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\form; defined('MOODLE_INTERNAL') || die; use context; use context_course; use core_form\dynamic_form; use grade_category; use grade_item; use grade_plugin_return; use grade_scale; use moodle_url; require_once($CFG->dirroot.'/grade/lib.php'); /** * Prints the add item gradebook form * * @copyright 2023 Mathew May <mathew.solutions> * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package core_grades */ class add_item extends dynamic_form { /** Grade plugin return tracking object. * @var object $gpr */ public $gpr; /** * Helper function to grab the current grade item based on information within the form. * * @return array * @throws \moodle_exception */ private function get_gradeitem(): array { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', null, PARAM_INT); if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { $item = $gradeitem->get_record_data(); $parentcategory = $gradeitem->get_parent_category(); } else { $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); $item = $gradeitem->get_record_data(); $parentcategory = grade_category::fetch_course_category($courseid); } $item->parentcategory = $parentcategory->id; $decimalpoints = $gradeitem->get_decimals(); if ($item->hidden > 1) { $item->hiddenuntil = $item->hidden; $item->hidden = 0; } else { $item->hiddenuntil = 0; } $item->locked = !empty($item->locked); $item->grademax = format_float($item->grademax, $decimalpoints); $item->grademin = format_float($item->grademin, $decimalpoints); if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $item->aggregationcoef = $item->aggregationcoef == 0 ? 0 : 1; } else { $item->aggregationcoef = format_float($item->aggregationcoef, 4); } if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0); } $item->cancontrolvisibility = $gradeitem->can_control_visibility(); return [ 'gradeitem' => $gradeitem, 'item' => $item ]; } /** * Form definition * * @return void * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ protected function definition() { global $CFG; $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', 0, PARAM_INT); $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); if ($gprplugin && ($gprplugin !== 'tree')) { $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); } else { $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); } $mform =& $this->_form; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; // Hidden elements. $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); $mform->addElement('hidden', 'itemid', $id); $mform->setType('itemid', PARAM_INT); $mform->addElement('hidden', 'itemtype', 'manual'); // All new items are manual only. $mform->setType('itemtype', PARAM_ALPHA); // Visible elements. $mform->addElement('text', 'itemname', get_string('itemname', 'grades')); $mform->setType('itemname', PARAM_TEXT); if (!empty($item->id)) { // If grades exist set a message so the user knows why they can not alter the grade type or scale. // We could never change the grade type for external items, so only need to show this for manual grade items. if ($gradeitem->has_grades() && !$gradeitem->is_external_item()) { // Set a message so the user knows why they can not alter the grade type or scale. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $gradesexistmsg = get_string('modgradecantchangegradetyporscalemsg', 'grades'); } else { $gradesexistmsg = get_string('modgradecantchangegradetypemsg', 'grades'); } $gradesexisthtml = '<div class=\'alert\'>' . $gradesexistmsg . '</div>'; $mform->addElement('static', 'gradesexistmsg', '', $gradesexisthtml); } } // Manual grade items cannot have grade type GRADE_TYPE_NONE. $mform->addElement('select', 'gradetype', get_string('gradetype', 'grades'), [ GRADE_TYPE_VALUE => get_string('typevalue', 'grades'), GRADE_TYPE_SCALE => get_string('typescale', 'grades'), GRADE_TYPE_TEXT => get_string('typetext', 'grades') ]); $mform->addHelpButton('gradetype', 'gradetype', 'grades'); $mform->setDefault('gradetype', GRADE_TYPE_VALUE); $options = [0 => get_string('usenoscale', 'grades')]; if ($scales = grade_scale::fetch_all_local($courseid)) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } if ($scales = grade_scale::fetch_all_global()) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } $mform->addElement('select', 'scaleid', get_string('scale'), $options); $mform->addHelpButton('scaleid', 'typescale', 'grades'); $mform->hideIf('scaleid', 'gradetype', 'noteq', GRADE_TYPE_SCALE); $mform->addElement('select', 'rescalegrades', get_string('modgraderescalegrades', 'grades'), [ '' => get_string('choose'), 'no' => get_string('no'), 'yes' => get_string('yes') ]); $mform->addHelpButton('rescalegrades', 'modgraderescalegrades', 'grades'); $mform->hideIf('rescalegrades', 'gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->addElement('float', 'grademax', get_string('grademax', 'grades')); $mform->addHelpButton('grademax', 'grademax', 'grades'); $mform->hideIf('grademax', 'gradetype', 'noteq', GRADE_TYPE_VALUE); if (get_config('moodle', 'grade_report_showmin')) { $mform->addElement('float', 'grademin', get_string('grademin', 'grades')); $mform->addHelpButton('grademin', 'grademin', 'grades'); $mform->hideIf('grademin', 'gradetype', 'noteq', GRADE_TYPE_VALUE); } // Hiding. if ($item->cancontrolvisibility) { $mform->addElement('advcheckbox', 'hidden', get_string('hidden', 'grades'), '', [], [0, 1]); $mform->hideIf('hidden', 'hiddenuntil[enabled]', 'checked'); } else { $mform->addElement('static', 'hidden', get_string('hidden', 'grades'), get_string('componentcontrolsvisibility', 'grades')); // Unset hidden to avoid data override. unset($item->hidden); } $mform->addHelpButton('hidden', 'hidden', 'grades'); // Locking. $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades')); $mform->addHelpButton('locked', 'locked', 'grades'); // Weight overrides. $mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades')); $mform->addHelpButton('weightoverride', 'weightoverride', 'grades'); $mform->hideIf('weightoverride', 'gradetype', 'eq', GRADE_TYPE_NONE); $mform->hideIf('weightoverride', 'gradetype', 'eq', GRADE_TYPE_TEXT); // Parent category related settings. $mform->addElement('float', 'aggregationcoef2', get_string('weight', 'grades')); $mform->addHelpButton('aggregationcoef2', 'weight', 'grades'); $mform->hideIf('aggregationcoef2', 'weightoverride'); $mform->hideIf('aggregationcoef2', 'gradetype', 'eq', GRADE_TYPE_NONE); $mform->hideIf('aggregationcoef2', 'gradetype', 'eq', GRADE_TYPE_TEXT); $options = []; $categories = grade_category::fetch_all(['courseid' => $courseid]); foreach ($categories as $cat) { $cat->apply_forced_settings(); $options[$cat->id] = $cat->get_name(); } if (count($categories) > 1) { $mform->addElement('select', 'parentcategory', get_string('gradecategory', 'grades'), $options); } $parentcategory = $gradeitem->get_parent_category(); if (!$parentcategory) { // If we do not have an id, we are creating a new grade item. // Assign the course category to this grade item. $parentcategory = grade_category::fetch_course_category($courseid); $gradeitem->parent_category = $parentcategory; } if ($gradeitem->is_external_item()) { // Following items are set up from modules and should not be overrided by user. if ($mform->elementExists('grademin')) { // The site setting grade_report_showmin may have prevented grademin being added to the form. $mform->hardFreeze('grademin'); } $mform->hardFreeze('itemname,gradetype,grademax,scaleid'); // For external items we can not change the grade type, even if no grades exist, so if it is set to // scale, then remove the grademax and grademin fields from the form - no point displaying them. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $mform->removeElement('grademax'); if ($mform->elementExists('grademin')) { $mform->removeElement('grademin'); } } else { // Not using scale, so remove it. $mform->removeElement('scaleid'); } // Always remove the rescale grades element if it's an external item. $mform->removeElement('rescalegrades'); } else if ($gradeitem->has_grades()) { // Can't change the grade type or the scale if there are grades. $mform->hardFreeze('gradetype, scaleid'); // If we are using scales then remove the unnecessary rescale and grade fields. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $mform->removeElement('rescalegrades'); $mform->removeElement('grademax'); if ($mform->elementExists('grademin')) { $mform->removeElement('grademin'); } } else { // Remove the scale field. $mform->removeElement('scaleid'); // Set the maximum grade to disabled unless a grade is chosen. $mform->hideIf('grademax', 'rescalegrades', 'eq', ''); } } else { // Remove rescale element if there are no grades. $mform->removeElement('rescalegrades'); } // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents!!! if ($id > -1 && $mform->elementExists('parentcategory')) { $mform->hardFreeze('parentcategory'); } $parentcategory->apply_forced_settings(); if (!$parentcategory->is_aggregationcoef_used()) { if ($mform->elementExists('aggregationcoef')) { $mform->removeElement('aggregationcoef'); } } else { $coefstring = $gradeitem->get_coefstring(); if ($coefstring !== '') { if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { // The advcheckbox is not compatible with disabledIf! $coefstring = 'aggregationcoefextrasum'; $element =& $mform->createElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades')); } else { $element =& $mform->createElement('text', 'aggregationcoef', get_string($coefstring, 'grades')); $mform->setType('aggregationcoef', PARAM_FLOAT); } if ($mform->elementExists('parentcategory')) { $mform->insertElementBefore($element, 'parentcategory'); } else { $mform->insertElementBefore($element, 'aggregationcoef2'); } $mform->addHelpButton('aggregationcoef', $coefstring, 'grades'); } $mform->hideIf('aggregationcoef', 'gradetype', 'eq', GRADE_TYPE_NONE); $mform->hideIf('aggregationcoef', 'gradetype', 'eq', GRADE_TYPE_TEXT); $mform->hideIf('aggregationcoef', 'parentcategory', 'eq', $parentcategory->id); } // Remove fields used by natural weighting if the parent category is not using natural weighting. // Or if the item is a scale and scales are not used in aggregation. if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE)) { if ($mform->elementExists('weightoverride')) { $mform->removeElement('weightoverride'); } if ($mform->elementExists('aggregationcoef2')) { $mform->removeElement('aggregationcoef2'); } } if ($category = $gradeitem->get_item_category()) { if ($category->aggregation == GRADE_AGGREGATE_SUM) { if ($mform->elementExists('gradetype')) { $mform->hardFreeze('gradetype'); } if ($mform->elementExists('grademin')) { $mform->hardFreeze('grademin'); } if ($mform->elementExists('grademax')) { $mform->hardFreeze('grademax'); } if ($mform->elementExists('scaleid')) { $mform->removeElement('scaleid'); } } } $url = new moodle_url('/grade/edit/tree/item.php', ['id' => $id, 'courseid' => $courseid]); $url = $this->gpr->add_url_params($url); $url = '<a class="showadvancedform" href="' . $url . '">' . get_string('showmore', 'form') .'</a>'; $mform->addElement('static', 'advancedform', $url); // Add return tracking info. $this->gpr->add_mform_elements($mform); $this->set_data($item); } /** * Return form context * * @return context */ protected function get_context_for_dynamic_submission(): context { $courseid = $this->optional_param('courseid', null, PARAM_INT); return context_course::instance($courseid); } /** * Check if current user has access to this form, otherwise throw exception * * @return void * @throws \required_capability_exception */ protected function check_access_for_dynamic_submission(): void { $courseid = $this->optional_param('courseid', null, PARAM_INT); require_capability('moodle/grade:manage', context_course::instance($courseid)); } /** * Load in existing data as form defaults * * @return void */ public function set_data_for_dynamic_submission(): void { $this->set_data((object)[ 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT) ]); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url * @throws \moodle_exception */ protected function get_page_url_for_dynamic_submission(): moodle_url { $params = [ 'id' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT), ]; return new moodle_url('/grade/edit/tree/index.php', $params); } /** * Process the form submission, used if form was submitted via AJAX * * @return array * @throws \moodle_exception */ public function process_dynamic_submission() { $data = $this->get_data(); $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; $parentcategory = grade_category::fetch_course_category($data->courseid); // Form submission handling. // This is a new item, and the category chosen is different than the default category. if (empty($gradeitem->id) && isset($data->parentcategory) && $parentcategory->id != $data->parentcategory) { $parentcategory = grade_category::fetch(['id' => $data->parentcategory]); } // If unset, give the aggregation values a default based on parent aggregation method. $defaults = grade_category::get_default_aggregation_coefficient_values($parentcategory->aggregation); if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') { $data->aggregationcoef = $defaults['aggregationcoef']; } if (!isset($data->weightoverride)) { $data->weightoverride = $defaults['weightoverride']; } if (!isset($data->gradepass) || $data->gradepass == '') { $data->gradepass = 0; } if (!isset($data->grademin) || $data->grademin == '') { $data->grademin = 0; } $hide = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil; if (!$hide) { $hide = empty($data->hidden) ? 0 : $data->hidden; } $locked = empty($data->locked) ? 0 : $data->locked; $locktime = empty($data->locktime) ? 0 : $data->locktime; $convert = ['grademax', 'grademin', 'aggregationcoef', 'aggregationcoef2']; foreach ($convert as $param) { if (property_exists($data, $param)) { $data->$param = unformat_float($data->$param); } } if (isset($data->aggregationcoef2) && $parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0; } else { $data->aggregationcoef2 = $defaults['aggregationcoef2']; } $oldmin = $gradeitem->grademin; $oldmax = $gradeitem->grademax; grade_item::set_properties($gradeitem, $data); $gradeitem->outcomeid = null; // Handle null decimals value. if (!property_exists($data, 'decimals') || $data->decimals < 0) { $gradeitem->decimals = null; } if (empty($gradeitem->id)) { $gradeitem->itemtype = 'manual'; // All new items to be manual only. $gradeitem->insert(); // Set parent if needed. if (isset($data->parentcategory)) { $gradeitem->set_parent($data->parentcategory, false); } } else { $gradeitem->update(); if (!empty($data->rescalegrades) && $data->rescalegrades == 'yes') { $newmin = $gradeitem->grademin; $newmax = $gradeitem->grademax; $gradeitem->rescale_grades_keep_percentage($oldmin, $oldmax, $newmin, $newmax, 'gradebook'); } } if ($item->cancontrolvisibility) { // Update hiding flag. $gradeitem->set_hidden($hide, true); } $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. $gradeitem->set_locked($locked); return [ 'result' => true, 'url' => $url, 'errors' => [], ]; } /** * Form validation. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $errors = []; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; if (isset($data['gradetype']) && $data['gradetype'] == GRADE_TYPE_SCALE) { if (empty($data['scaleid'])) { $errors['scaleid'] = get_string('missingscale', 'grades'); } } // We need to make all the validations related with grademax and grademin // with them being correct floats, keeping the originals unmodified for // later validations / showing the form back... // TODO: Note that once MDL-73994 is fixed we'll have to re-visit this and // adapt the code below to the new values arriving here, without forgetting // the special case of empties and nulls. $grademax = isset($data['grademax']) ? unformat_float($data['grademax']) : null; $grademin = isset($data['grademin']) ? unformat_float($data['grademin']) : null; if (!is_null($grademin) && !is_null($grademax)) { if ($grademax == $grademin || $grademax < $grademin) { $errors['grademin'] = get_string('incorrectminmax', 'grades'); $errors['grademax'] = get_string('incorrectminmax', 'grades'); } } // We do not want the user to be able to change the grade type or scale for this item if grades exist. if ($gradeitem && $gradeitem->has_grades()) { // Check that grade type is set - should never not be set unless form has been modified. if (!isset($data['gradetype'])) { $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades'); } else if ($data['gradetype'] !== $gradeitem->gradetype) { // Check if we are changing the grade type. $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades'); } else if ($data['gradetype'] == GRADE_TYPE_SCALE) { // Check if we are changing the scale - can't do this when grades exist. if (isset($data['scaleid']) && ($data['scaleid'] !== $gradeitem->scaleid)) { $errors['scaleid'] = get_string('modgradecantchangescale', 'grades'); } } } if ($gradeitem) { if ($gradeitem->gradetype == GRADE_TYPE_VALUE) { if ((((bool) get_config('moodle', 'grade_report_showmin')) && grade_floats_different($grademin, $gradeitem->grademin)) || grade_floats_different($grademax, $gradeitem->grademax)) { if ($gradeitem->has_grades() && empty($data['rescalegrades'])) { $errors['rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades'); } } } } return $errors; } } classes/form/add_category.php 0000604 00000072765 15062070555 0012324 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\form; use context; use context_course; use core_form\dynamic_form; use grade_category; use grade_edit_tree; use grade_helper; use grade_item; use grade_plugin_return; use grade_scale; use moodle_url; defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/grade/lib.php'); require_once($CFG->dirroot . '/grade/edit/tree/lib.php'); /** * Prints the add category gradebook form * * @copyright 2023 Ilya Tregubov <ilya@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package core_grades */ class add_category extends dynamic_form { /** Grade plugin return tracking object. * @var object $gpr */ public $gpr; /** Available aggregations. * @var array|null $aggregation_options */ private ?array $aggregation_options; /** * Helper function to grab the current grade category based on information within the form. * * @return array * @throws \moodle_exception */ private function get_gradecategory(): array { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('category', null, PARAM_INT); if ($gradecategory = grade_category::fetch(['id' => $id, 'courseid' => $courseid])) { $gradecategory->apply_forced_settings(); $category = $gradecategory->get_record_data(); // Set parent. $category->parentcategory = $gradecategory->parent; $gradeitem = $gradecategory->load_grade_item(); // Normalize coef values if needed. $parentcategory = $gradecategory->get_parent_category(); foreach ($gradeitem->get_record_data() as $key => $value) { $category->{"grade_item_$key"} = $value; } $decimalpoints = $gradeitem->get_decimals(); $category->grade_item_grademax = format_float($category->grade_item_grademax, $decimalpoints); $category->grade_item_grademin = format_float($category->grade_item_grademin, $decimalpoints); $category->grade_item_gradepass = format_float($category->grade_item_gradepass, $decimalpoints); $category->grade_item_multfactor = format_float($category->grade_item_multfactor, 4); $category->grade_item_plusfactor = format_float($category->grade_item_plusfactor, 4); $category->grade_item_aggregationcoef2 = format_float($category->grade_item_aggregationcoef2 * 100.0, 4); if (isset($parentcategory)) { if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $category->grade_item_aggregationcoef = $category->grade_item_aggregationcoef == 0 ? 0 : 1; } else { $category->grade_item_aggregationcoef = format_float($category->grade_item_aggregationcoef, 4); } } // Check if the gradebook is frozen. This allows grades not be altered at all until a user verifies that they // wish to update the grades. $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid); // Stick with the original code if the grade book is frozen. if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { if ($category->aggregation == GRADE_AGGREGATE_SUM) { // Input fields for grademin and grademax are disabled for the "Natural" category, // this means they will be ignored if user does not change aggregation method. // But if user does change aggregation method the default values should be used. $category->grademax = 100; $category->grade_item_grademax = 100; $category->grademin = 0; $category->grade_item_grademin = 0; } } else { if ($category->aggregation == GRADE_AGGREGATE_SUM && !$gradeitem->is_calculated()) { // Input fields for grademin and grademax are disabled for the "Natural" category, // this means they will be ignored if user does not change aggregation method. // But if user does change aggregation method the default values should be used. // This does not apply to calculated category totals. $category->grademax = 100; $category->grade_item_grademax = 100; $category->grademin = 0; $category->grade_item_grademin = 0; } } } else { $gradecategory = new grade_category(['courseid' => $courseid], false); $gradecategory->apply_default_settings(); $gradecategory->apply_forced_settings(); $category = $gradecategory->get_record_data(); $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); foreach ($gradeitem->get_record_data() as $key => $value) { $category->{"grade_item_$key"} = $value; } } return [ 'gradecategory' => $gradecategory, 'categoryitem' => $category, 'gradeitem' => $gradeitem ]; } /** * Form definition * * @return void * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ protected function definition(): void { global $CFG, $OUTPUT, $COURSE; $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('category', 0, PARAM_INT); $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); if ($gprplugin && ($gprplugin !== 'tree')) { $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); } else { $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); } $mform = $this->_form; $this->aggregation_options = grade_helper::get_aggregation_strings(); $local = $this->get_gradecategory(); $category = $local['categoryitem']; // Hidden elements. $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); $mform->addElement('hidden', 'category', $id); $mform->setType('category', PARAM_INT); // Visible elements. $mform->addElement('text', 'fullname', get_string('categoryname', 'grades')); $mform->setType('fullname', PARAM_TEXT); $mform->addRule('fullname', null, 'required', null, 'client'); $mform->addElement('select', 'aggregation', get_string('aggregation', 'grades'), $this->aggregation_options); $mform->addHelpButton('aggregation', 'aggregation', 'grades'); $mform->addElement('checkbox', 'aggregateonlygraded', get_string('aggregateonlygraded', 'grades')); $mform->addHelpButton('aggregateonlygraded', 'aggregateonlygraded', 'grades'); if (empty($CFG->enableoutcomes)) { $mform->addElement('hidden', 'aggregateoutcomes'); $mform->setType('aggregateoutcomes', PARAM_INT); } else { $mform->addElement('checkbox', 'aggregateoutcomes', get_string('aggregateoutcomes', 'grades')); $mform->addHelpButton('aggregateoutcomes', 'aggregateoutcomes', 'grades'); } $mform->addElement('text', 'keephigh', get_string('keephigh', 'grades'), 'size="3"'); $mform->setType('keephigh', PARAM_INT); $mform->addHelpButton('keephigh', 'keephigh', 'grades'); $mform->addElement('text', 'droplow', get_string('droplow', 'grades'), 'size="3"'); $mform->setType('droplow', PARAM_INT); $mform->addHelpButton('droplow', 'droplow', 'grades'); $mform->hideIf('droplow', 'keephigh', 'noteq', 0); $mform->hideIf('keephigh', 'droplow', 'noteq', 0); $mform->hideIf('droplow', 'keephigh', 'noteq', 0); if (!empty($category->id)) { $gradeitem = $local['gradeitem']; // If grades exist set a message so the user knows why they can not alter the grade type or scale. // We could never change the grade type for external items, so only need to show this for manual grade items. if ($gradeitem->has_overridden_grades()) { // Set a message so the user knows why the can not alter the grade type or scale. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $gradesexistmsg = get_string('modgradecategorycantchangegradetyporscalemsg', 'grades'); } else { $gradesexistmsg = get_string('modgradecategorycantchangegradetypemsg', 'grades'); } $notification = new \core\output\notification($gradesexistmsg, \core\output\notification::NOTIFY_INFO); $notification->set_show_closebutton(false); $mform->addElement('static', 'gradesexistmsg', '', $OUTPUT->render($notification)); } } $options = [ GRADE_TYPE_NONE => get_string('typenone', 'grades'), GRADE_TYPE_VALUE => get_string('typevalue', 'grades'), GRADE_TYPE_SCALE => get_string('typescale', 'grades'), GRADE_TYPE_TEXT => get_string('typetext', 'grades') ]; $mform->addElement('select', 'grade_item_gradetype', get_string('gradetype', 'grades'), $options); $mform->addHelpButton('grade_item_gradetype', 'gradetype', 'grades'); $mform->setDefault('grade_item_gradetype', GRADE_TYPE_VALUE); $mform->hideIf('grade_item_gradetype', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); $options = [0 => get_string('usenoscale', 'grades')]; if ($scales = grade_scale::fetch_all_local($COURSE->id)) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } if ($scales = grade_scale::fetch_all_global()) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } // Ugly BC hack - it was possible to use custom scale from other courses. if (!empty($category->grade_item_scaleid) && !isset($options[$category->grade_item_scaleid])) { if ($scale = grade_scale::fetch(['id' => $category->grade_item_scaleid])) { $options[$scale->id] = $scale->get_name().' '.get_string('incorrectcustomscale', 'grades'); } } $mform->addElement('select', 'grade_item_scaleid', get_string('scale'), $options); $mform->addHelpButton('grade_item_scaleid', 'typescale', 'grades'); $mform->hideIf('grade_item_scaleid', 'grade_item_gradetype', 'noteq', GRADE_TYPE_SCALE); $mform->hideIf('grade_item_scaleid', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); $choices = []; $choices[''] = get_string('choose'); $choices['no'] = get_string('no'); $choices['yes'] = get_string('yes'); $mform->addElement('select', 'grade_item_rescalegrades', get_string('modgradecategoryrescalegrades', 'grades'), $choices); $mform->addHelpButton('grade_item_rescalegrades', 'modgradecategoryrescalegrades', 'grades'); $mform->hideIf('grade_item_rescalegrades', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->addElement('float', 'grade_item_grademax', get_string('grademax', 'grades')); $mform->addHelpButton('grade_item_grademax', 'grademax', 'grades'); $mform->hideIf('grade_item_grademax', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->hideIf('grade_item_grademax', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); if ((bool) get_config('moodle', 'grade_report_showmin')) { $mform->addElement('float', 'grade_item_grademin', get_string('grademin', 'grades')); $mform->addHelpButton('grade_item_grademin', 'grademin', 'grades'); $mform->hideIf('grade_item_grademin', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->hideIf('grade_item_grademin', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); } // Hiding. // advcheckbox is not compatible with disabledIf! $mform->addElement('checkbox', 'grade_item_hidden', get_string('hidden', 'grades')); $mform->addHelpButton('grade_item_hidden', 'hidden', 'grades'); // Locking. $mform->addElement('checkbox', 'grade_item_locked', get_string('locked', 'grades')); $mform->addHelpButton('grade_item_locked', 'locked', 'grades'); $mform->addElement('advcheckbox', 'grade_item_weightoverride', get_string('adjustedweight', 'grades')); $mform->addHelpButton('grade_item_weightoverride', 'weightoverride', 'grades'); $mform->addElement('float', 'grade_item_aggregationcoef2', get_string('weight', 'grades')); $mform->addHelpButton('grade_item_aggregationcoef2', 'weight', 'grades'); $mform->hideIf('grade_item_aggregationcoef2', 'grade_item_weightoverride'); $options = []; $default = -1; $categories = grade_category::fetch_all(['courseid' => $courseid]); foreach ($categories as $cat) { $cat->apply_forced_settings(); $options[$cat->id] = $cat->get_name(); if ($cat->is_course_category()) { $default = $cat->id; } } if (count($categories) > 1) { $mform->addElement('select', 'parentcategory', get_string('parentcategory', 'grades'), $options); $mform->setDefault('parentcategory', $default); } $params = ['courseid' => $courseid]; if ($id > 0) { $params['id'] = $id; } $url = new moodle_url('/grade/edit/tree/category.php', $params); $url = $this->gpr->add_url_params($url); $url = '<a class="showadvancedform" href="' . $url . '">' . get_string('showmore', 'form') .'</a>'; $mform->addElement('static', 'advancedform', $url); // Add return tracking info. $this->gpr->add_mform_elements($mform); $this->set_data($category); } /** * This method implements changes to the form that need to be made once the form data is set. */ public function definition_after_data(): void { global $CFG; $mform =& $this->_form; $categoryobject = new grade_category(); foreach ($categoryobject->forceable as $property) { if ((int)$CFG->{"grade_{$property}_flag"} & 1) { if ($mform->elementExists($property)) { if (empty($CFG->grade_hideforcedsettings)) { $mform->hardFreeze($property); } else { if ($mform->elementExists($property)) { $mform->removeElement($property); } } } } } if ($CFG->grade_droplow > 0) { if ($mform->elementExists('keephigh')) { $mform->removeElement('keephigh'); } } else if ($CFG->grade_keephigh > 0) { if ($mform->elementExists('droplow')) { $mform->removeElement('droplow'); } } if ($id = $mform->getElementValue('id')) { $gradecategory = grade_category::fetch(['id' => $id]); $gradeitem = $gradecategory->load_grade_item(); // Remove agg coef if not used. if ($gradecategory->is_course_category()) { if ($mform->elementExists('parentcategory')) { $mform->removeElement('parentcategory'); } } else { // If we wanted to change parent of existing category // we would have to verify there are no circular references in parents!!! if ($mform->elementExists('parentcategory')) { $mform->hardFreeze('parentcategory'); } } // Prevent the user from using drop lowest/keep highest when the aggregation method cannot handle it. if (!$gradecategory->can_apply_limit_rules()) { if ($mform->elementExists('keephigh')) { $mform->setConstant('keephigh', 0); $mform->hardFreeze('keephigh'); } if ($mform->elementExists('droplow')) { $mform->setConstant('droplow', 0); $mform->hardFreeze('droplow'); } } if ($gradeitem->is_calculated()) { $gradesexistmsg = get_string('calculationwarning', 'grades'); $gradesexisthtml = '<div class=\'alert alert-warning\'>' . $gradesexistmsg . '</div>'; $mform->addElement('static', 'gradesexistmsg', '', $gradesexisthtml); // Following elements are ignored when calculation formula used. if ($mform->elementExists('aggregation')) { $mform->removeElement('aggregation'); } if ($mform->elementExists('keephigh')) { $mform->removeElement('keephigh'); } if ($mform->elementExists('droplow')) { $mform->removeElement('droplow'); } if ($mform->elementExists('aggregateonlygraded')) { $mform->removeElement('aggregateonlygraded'); } if ($mform->elementExists('aggregateoutcomes')) { $mform->removeElement('aggregateoutcomes'); } } // If it is a course category, remove the "required" rule from the "fullname" element. if ($gradecategory->is_course_category()) { unset($mform->_rules['fullname']); $key = array_search('fullname', $mform->_required); unset($mform->_required[$key]); } // If it is a course category and its fullname is ?, show an empty field. if ($gradecategory->is_course_category() && $mform->getElementValue('fullname') == '?') { $mform->setDefault('fullname', ''); } // Remove unwanted aggregation options. if ($mform->elementExists('aggregation')) { $allaggoptions = array_keys($this->aggregation_options); $aggel =& $mform->getElement('aggregation'); $visible = explode(',', $CFG->grade_aggregations_visible); if (!is_null($gradecategory->aggregation)) { // Current type is always visible. $visible[] = $gradecategory->aggregation; } foreach ($allaggoptions as $type) { if (!in_array($type, $visible)) { $aggel->removeOption($type); } } } } else { // Adding new category // Remove unwanted aggregation options. if ($mform->elementExists('aggregation')) { $allaggoptions = array_keys($this->aggregation_options); $aggel =& $mform->getElement('aggregation'); $visible = explode(',', $CFG->grade_aggregations_visible); foreach ($allaggoptions as $type) { if (!in_array($type, $visible)) { $aggel->removeOption($type); } } } $mform->removeElement('grade_item_rescalegrades'); } // Grade item. if ($id = $mform->getElementValue('id')) { $gradecategory = grade_category::fetch(['id' => $id]); $gradeitem = $gradecategory->load_grade_item(); // Load appropriate "hidden"/"hidden until" defaults. if (!$gradeitem->is_hiddenuntil()) { $mform->setDefault('grade_item_hidden', $gradeitem->get_hidden()); } if ($gradeitem->has_overridden_grades()) { // Can't change the grade type or the scale if there are grades. $mform->hardFreeze('grade_item_gradetype, grade_item_scaleid'); // If we are using scales then remove the unnecessary rescale and grade fields. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $mform->removeElement('grade_item_rescalegrades'); $mform->removeElement('grade_item_grademax'); if ($mform->elementExists('grade_item_grademin')) { $mform->removeElement('grade_item_grademin'); } } else { // Not using scale, so remove it. $mform->removeElement('grade_item_scaleid'); $mform->hideIf('grade_item_grademax', 'grade_item_rescalegrades', 'eq', ''); $mform->hideIf('grade_item_grademin', 'grade_item_rescalegrades', 'eq', ''); } } else { // Remove the rescale element if there are no grades. $mform->removeElement('grade_item_rescalegrades'); } // Remove the aggregation coef element if not needed. if ($gradeitem->is_course_item()) { if ($mform->elementExists('grade_item_aggregationcoef')) { $mform->removeElement('grade_item_aggregationcoef'); } if ($mform->elementExists('grade_item_weightoverride')) { $mform->removeElement('grade_item_weightoverride'); } if ($mform->elementExists('grade_item_aggregationcoef2')) { $mform->removeElement('grade_item_aggregationcoef2'); } } else { if ($gradeitem->is_category_item()) { $category = $gradeitem->get_item_category(); $parentcategory = $category->get_parent_category(); } else { $parentcategory = $gradeitem->get_parent_category(); } $parentcategory->apply_forced_settings(); if (!$parentcategory->is_aggregationcoef_used()) { if ($mform->elementExists('grade_item_aggregationcoef')) { $mform->removeElement('grade_item_aggregationcoef'); } } else { $coefstring = $gradeitem->get_coefstring(); if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { // Advcheckbox is not compatible with disabledIf! $coefstring = 'aggregationcoefextrasum'; $element =& $mform->createElement('checkbox', 'grade_item_aggregationcoef', get_string($coefstring, 'grades')); } else { $element =& $mform->createElement('text', 'grade_item_aggregationcoef', get_string($coefstring, 'grades')); $mform->setType('grade_item_aggregationcoef', PARAM_FLOAT); } $mform->insertElementBefore($element, 'parentcategory'); $mform->addHelpButton('grade_item_aggregationcoef', $coefstring, 'grades'); } // Remove fields used by natural weighting if the parent category is not using natural weighting. // Or if the item is a scale and scales are not used in aggregation. if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE)) { if ($mform->elementExists('grade_item_weightoverride')) { $mform->removeElement('grade_item_weightoverride'); } if ($mform->elementExists('grade_item_aggregationcoef2')) { $mform->removeElement('grade_item_aggregationcoef2'); } } } } } /** * Return form context * * @return context */ protected function get_context_for_dynamic_submission(): context { $courseid = $this->optional_param('courseid', null, PARAM_INT); return context_course::instance($courseid); } /** * Check if current user has access to this form, otherwise throw exception * * @return void * @throws \required_capability_exception */ protected function check_access_for_dynamic_submission(): void { $courseid = $this->optional_param('courseid', null, PARAM_INT); require_capability('moodle/grade:manage', context_course::instance($courseid)); } /** * Load in existing data as form defaults * * @return void */ public function set_data_for_dynamic_submission(): void { $this->set_data((object)[ 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 'category' => $this->optional_param('category', null, PARAM_INT) ]); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url * @throws \moodle_exception */ protected function get_page_url_for_dynamic_submission(): moodle_url { $params = [ 'id' => $this->optional_param('courseid', null, PARAM_INT), 'category' => $this->optional_param('category', null, PARAM_INT), ]; return new moodle_url('/grade/edit/tree/index.php', $params); } /** * Process the form submission, used if form was submitted via AJAX * * @return array * @throws \moodle_exception */ public function process_dynamic_submission(): array { $data = $this->get_data(); $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); $local = $this->get_gradecategory(); $gradecategory = $local['gradecategory']; grade_edit_tree::update_gradecategory($gradecategory, $data); return [ 'result' => true, 'url' => $url, 'errors' => [], ]; } /** * Form validation. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $gradeitem = false; if ($data['id']) { $gradecategory = grade_category::fetch(['id' => $data['id']]); $gradeitem = $gradecategory->load_grade_item(); } $errors = parent::validation($data, $files); if (array_key_exists('grade_item_gradetype', $data) && $data['grade_item_gradetype'] == GRADE_TYPE_SCALE) { if (empty($data['grade_item_scaleid'])) { $errors['grade_item_scaleid'] = get_string('missingscale', 'grades'); } } // We need to make all the validations related with grademax and grademin // with them being correct floats, keeping the originals unmodified for // later validations / showing the form back... // TODO: Note that once MDL-73994 is fixed we'll have to re-visit this and // adapt the code below to the new values arriving here, without forgetting // the special case of empties and nulls. $grademax = isset($data['grade_item_grademax']) ? unformat_float($data['grade_item_grademax']) : null; $grademin = isset($data['grade_item_grademin']) ? unformat_float($data['grade_item_grademin']) : null; if (!is_null($grademin) && !is_null($grademax)) { if (($grademax != 0 || $grademin != 0) && ($grademax == $grademin || $grademax < $grademin)) { $errors['grade_item_grademin'] = get_string('incorrectminmax', 'grades'); $errors['grade_item_grademax'] = get_string('incorrectminmax', 'grades'); } } if ($data['id'] && $gradeitem->has_overridden_grades()) { if ($gradeitem->gradetype == GRADE_TYPE_VALUE) { if (grade_floats_different($grademin, $gradeitem->grademin) || grade_floats_different($grademax, $gradeitem->grademax)) { if (empty($data['grade_item_rescalegrades'])) { $errors['grade_item_rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades'); } } } } return $errors; } } classes/form/add_outcome.php 0000604 00000047432 15062070555 0012153 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\form; defined('MOODLE_INTERNAL') || die; use context; use context_course; use core_form\dynamic_form; use grade_category; use grade_item; use grade_outcome; use grade_plugin_return; use moodle_url; require_once($CFG->dirroot.'/grade/lib.php'); /** * Prints the add outcome gradebook form. * * @copyright 2023 Mathew May <mathew.solutions> * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package core_grades */ class add_outcome extends dynamic_form { /** Grade plugin return tracking object. * @var object $gpr */ public $gpr; /** * Helper function to grab the current grade outcome item based on information within the form. * * @return array * @throws \moodle_exception */ private function get_gradeitem(): array { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', null, PARAM_INT); if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { // Redirect if outcomeid not present. if (empty($gradeitem->outcomeid)) { $url = new moodle_url('/grade/edit/tree/item.php', ['id' => $id, 'courseid' => $courseid]); redirect($this->gpr->add_url_params($url)); } $item = $gradeitem->get_record_data(); $parentcategory = $gradeitem->get_parent_category(); if ($item->itemtype == 'mod') { $cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance, $item->courseid); $item->cmid = $cm->id; } else { $item->cmid = 0; } } else { $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); $item = $gradeitem->get_record_data(); $parentcategory = grade_category::fetch_course_category($courseid); } $item->parentcategory = $parentcategory->id; if ($item->hidden > 1) { $item->hiddenuntil = $item->hidden; $item->hidden = 0; } else { $item->hiddenuntil = 0; } $item->locked = !empty($item->locked); if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $item->aggregationcoef = $item->aggregationcoef == 0 ? 0 : 1; } else { $item->aggregationcoef = format_float($item->aggregationcoef, 4); } if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0); } $item->cancontrolvisibility = $gradeitem->can_control_visibility(); return [ 'gradeitem' => $gradeitem, 'item' => $item ]; } /** * Form definition * * @return void * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ protected function definition() { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', 0, PARAM_INT); $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); if ($gprplugin && ($gprplugin !== 'tree')) { $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); } else { $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); } $mform =& $this->_form; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; // Hidden elements. $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); $mform->addElement('hidden', 'itemid', $id); $mform->setType('itemid', PARAM_INT); // Allow setting of outcomes on module items too. $outcomeoptions = []; if ($outcomes = grade_outcome::fetch_all_available($courseid)) { foreach ($outcomes as $outcome) { $outcomeoptions[$outcome->id] = $outcome->get_name(); } } // Visible elements. $mform->addElement('text', 'itemname', get_string('itemname', 'grades')); $mform->addRule('itemname', get_string('required'), 'required', null, 'client'); $mform->setType('itemname', PARAM_TEXT); $mform->addElement('selectwithlink', 'outcomeid', get_string('outcome', 'grades'), $outcomeoptions); $mform->addHelpButton('outcomeid', 'outcome', 'grades'); $mform->addRule('outcomeid', get_string('required'), 'required'); $options = [0 => get_string('none')]; if ($coursemods = get_course_mods($courseid)) { foreach ($coursemods as $coursemod) { if ($mod = get_coursemodule_from_id($coursemod->modname, $coursemod->id)) { $options[$coursemod->id] = format_string($mod->name); } } } $mform->addElement('select', 'cmid', get_string('linkedactivity', 'grades'), $options); $mform->addHelpButton('cmid', 'linkedactivity', 'grades'); $mform->setDefault('cmid', 0); // Hiding. $mform->addElement('checkbox', 'hidden', get_string('hidden', 'grades')); $mform->addHelpButton('hidden', 'hidden', 'grades'); // Locking. $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades')); $mform->addHelpButton('locked', 'locked', 'grades'); // Parent category related settings. $mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades')); $mform->addHelpButton('weightoverride', 'weightoverride', 'grades'); $mform->addElement('text', 'aggregationcoef2', get_string('weight', 'grades')); $mform->addHelpButton('aggregationcoef2', 'weight', 'grades'); $mform->setType('aggregationcoef2', PARAM_RAW); $mform->hideIf('aggregationcoef2', 'weightoverride'); $options = []; $coefstring = ''; $categories = grade_category::fetch_all(['courseid' => $courseid]); foreach ($categories as $cat) { $cat->apply_forced_settings(); $options[$cat->id] = $cat->get_name(); if ($cat->is_aggregationcoef_used()) { if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefweight') ? 'aggregationcoefweight' : 'aggregationcoef'; } else if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? 'aggregationcoefextrasum' : 'aggregationcoef'; } else if ($cat->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextraweight') ? 'aggregationcoefextraweight' : 'aggregationcoef'; } else if ($cat->aggregation == GRADE_AGGREGATE_SUM) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? 'aggregationcoefextrasum' : 'aggregationcoef'; } else { $coefstring = 'aggregationcoef'; } } else { $mform->disabledIf('aggregationcoef', 'parentcategory', 'eq', $cat->id); } } if (count($categories) > 1) { $mform->addElement('select', 'parentcategory', get_string('gradecategory', 'grades'), $options); $mform->disabledIf('parentcategory', 'cmid', 'noteq', 0); } if ($coefstring !== '') { if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { $coefstring = 'aggregationcoefextrasum'; $mform->addElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades')); } else { $mform->addElement('text', 'aggregationcoef', get_string($coefstring, 'grades')); } $mform->addHelpButton('aggregationcoef', $coefstring, 'grades'); } // Remove the aggregation coef element if not needed. if ($gradeitem->is_course_item()) { if ($mform->elementExists('parentcategory')) { $mform->removeElement('parentcategory'); } if ($mform->elementExists('aggregationcoef')) { $mform->removeElement('aggregationcoef'); } } else { // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents. if ($id > -1 && $mform->elementExists('parentcategory')) { $mform->hardFreeze('parentcategory'); } $parentcategory = $gradeitem->get_parent_category(); if (!$parentcategory) { // If we do not have an id, we are creating a new grade item. // Assign the course category to this grade item. $parentcategory = grade_category::fetch_course_category($courseid); $gradeitem->parent_category = $parentcategory; } $parentcategory->apply_forced_settings(); if (!$parentcategory->is_aggregationcoef_used() || !$parentcategory->aggregateoutcomes) { if ($mform->elementExists('aggregationcoef')) { $mform->removeElement('aggregationcoef'); } } else { // Fix label if needed. $agg_el =& $mform->getElement('aggregationcoef'); $aggcoef = ''; if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { $aggcoef = 'aggregationcoefweight'; } else if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $aggcoef = 'aggregationcoefextrasum'; } else if ($parentcategory->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { $aggcoef = 'aggregationcoefextraweight'; } else if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $aggcoef = 'aggregationcoefextrasum'; } if ($aggcoef !== '') { $agg_el->setLabel(get_string($aggcoef, 'grades')); $mform->addHelpButton('aggregationcoef', $aggcoef, 'grades'); } } // Remove the natural weighting fields for other aggregations, // or when the category does not aggregate outcomes. if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || !$parentcategory->aggregateoutcomes) { if ($mform->elementExists('weightoverride')) { $mform->removeElement('weightoverride'); } if ($mform->elementExists('aggregationcoef2')) { $mform->removeElement('aggregationcoef2'); } } } $url = new moodle_url('/grade/edit/tree/outcomeitem.php', ['id' => $id, 'courseid' => $courseid]); $url = $this->gpr->add_url_params($url); $url = '<a class="showadvancedform" href="' . $url . '">' . get_string('showmore', 'form') .'</a>'; $mform->addElement('static', 'advancedform', $url); // Add return tracking info. $this->gpr->add_mform_elements($mform); $this->set_data($item); } /** * Return form context * * @return context */ protected function get_context_for_dynamic_submission(): context { $courseid = $this->optional_param('courseid', null, PARAM_INT); return context_course::instance($courseid); } /** * Check if current user has access to this form, otherwise throw exception * * @return void * @throws \required_capability_exception */ protected function check_access_for_dynamic_submission(): void { $courseid = $this->optional_param('courseid', null, PARAM_INT); require_capability('moodle/grade:manage', context_course::instance($courseid)); } /** * Load in existing data as form defaults * * @return void */ public function set_data_for_dynamic_submission(): void { $this->set_data((object)[ 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT) ]); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url * @throws \moodle_exception */ protected function get_page_url_for_dynamic_submission(): moodle_url { $params = [ 'id' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT), ]; return new moodle_url('/grade/edit/tree/index.php', $params); } /** * Process the form submission, used if form was submitted via AJAX * * @return array * @throws \moodle_exception */ public function process_dynamic_submission() { global $DB; $data = $this->get_data(); $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; $parentcategory = grade_category::fetch_course_category($data->courseid); // Form submission handling. // If unset, give the aggregation values a default based on parent aggregation method. $defaults = grade_category::get_default_aggregation_coefficient_values($parentcategory->aggregation); if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') { $data->aggregationcoef = $defaults['aggregationcoef']; } if (!isset($data->weightoverride)) { $data->weightoverride = $defaults['weightoverride']; } if (property_exists($data, 'calculation')) { $data->calculation = grade_item::normalize_formula($data->calculation, $data->courseid); } $hide = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil; if (!$hide) { $hide = empty($data->hidden) ? 0 : $data->hidden; } $locked = empty($data->locked) ? 0 : $data->locked; $locktime = empty($data->locktime) ? 0 : $data->locktime; $convert = ['gradepass', 'aggregationcoef', 'aggregationcoef2']; foreach ($convert as $param) { if (property_exists($data, $param)) { $data->$param = unformat_float($data->$param); } } if (isset($data->aggregationcoef2) && $parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0; } else { $data->aggregationcoef2 = $defaults['aggregationcoef2']; } grade_item::set_properties($gradeitem, $data); // Link this outcome item to the user specified linked activity. if (empty($data->cmid) || $data->cmid == 0) { // Manual item. $gradeitem->itemtype = 'manual'; $gradeitem->itemmodule = null; $gradeitem->iteminstance = null; $gradeitem->itemnumber = 0; } else { $params = [$data->cmid]; $module = $DB->get_record_sql("SELECT cm.*, m.name as modname FROM {modules} m, {course_modules} cm WHERE cm.id = ? AND cm.module = m.id ", $params); $gradeitem->itemtype = 'mod'; $gradeitem->itemmodule = $module->modname; $gradeitem->iteminstance = $module->instance; if ($items = grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, 'iteminstance' => $gradeitem->iteminstance, 'courseid' => $data->courseid])) { if (!empty($gradeitem->id) && in_array($gradeitem, $items)) { // No change needed. } else { $max = 999; foreach ($items as $item) { if (empty($item->outcomeid)) { continue; } if ($item->itemnumber > $max) { $max = $item->itemnumber; } } $gradeitem->itemnumber = $max + 1; } } else { $gradeitem->itemnumber = 1000; } } // Fix scale used. $outcome = grade_outcome::fetch(['id' => $data->outcomeid]); $gradeitem->gradetype = GRADE_TYPE_SCALE; $gradeitem->scaleid = $outcome->scaleid; // TODO: we might recalculate existing outcome grades when changing scale. if (empty($gradeitem->id)) { $gradeitem->insert(); // Move next to activity if adding linked outcome. if ($gradeitem->itemtype == 'mod') { if ($linkeditem = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, 'iteminstance' => $gradeitem->iteminstance, 'itemnumber' => 0, 'courseid' => $data->courseid])) { $gradeitem->set_parent($linkeditem->categoryid); $gradeitem->move_after_sortorder($linkeditem->sortorder); } } else { // Set parent if needed. if (isset($data->parentcategory)) { $gradeitem->set_parent($data->parentcategory, false); } } } else { $gradeitem->update(); } if ($item->cancontrolvisibility) { // Update hiding flag. $gradeitem->set_hidden($hide, true); } $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. $gradeitem->set_locked($locked, false, true); return [ 'result' => true, 'url' => $url, 'errors' => [], ]; } /** * Form validation. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $errors = []; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; if (!grade_verify_idnumber($gradeitem->id, $item->courseid, $gradeitem)) { $errors['idnumber'] = get_string('idnumbertaken'); } return $errors; } } classes/grades/grader/gradingpanel/scale/external/fetch.php 0000604 00000017777 15062070555 0020104 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Web service functions relating to scale grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\scale\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use moodle_exception; use stdClass; /** * External grading panel scale API * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class fetch extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @return array * @throws \dml_exception * @throws \invalid_parameter_exception * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); if (!$gradeitem->is_using_scale()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales"); } $gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST); // One can access its own grades. Others just if they're graders. if ($gradeduserid != $USER->id) { $gradeitem->require_user_can_grade($gradeduser, $USER); } // Set up some items we need to return on other interfaces. $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; $maxgrade = (int) $gradeitem->get_grade_item()->grademax; return self::get_fetch_data($gradeitem, $gradeduser, $maxgrade, $gradername); } /** * Get the data to be fetched. * * @param gradeitem $gradeitem * @param stdClass $gradeduser * @param int $maxgrade * @param string|null $gradername * @return array */ public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser, int $maxgrade, ?string $gradername): array { global $USER; $hasgrade = $gradeitem->user_has_grade($gradeduser); $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER); $currentgrade = (int) unformat_float($grade->grade); $menu = $gradeitem->get_grade_menu(); $values = array_map(function($description, $value) use ($currentgrade) { return [ 'value' => $value, 'title' => $description, 'selected' => ($value == $currentgrade), ]; }, $menu, array_keys($menu)); return [ 'templatename' => 'core_grades/grades/grader/gradingpanel/scale', 'hasgrade' => $hasgrade, 'grade' => [ 'options' => $values, 'usergrade' => $grade->usergrade, 'maxgrade' => $maxgrade, 'gradedby' => $gradername, 'timecreated' => $grade->timecreated, 'timemodified' => $grade->timemodified, ], 'warnings' => [], ]; } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'), 'hasgrade' => new external_value(PARAM_BOOL, 'Does the user have a grade?'), 'grade' => new external_single_structure([ 'options' => new external_multiple_structure( new external_single_structure([ 'value' => new external_value(PARAM_FLOAT, 'The grade value'), 'title' => new external_value(PARAM_RAW, 'The description fo the option'), 'selected' => new external_value(PARAM_BOOL, 'Whether this item is currently selected'), ]), 'The description of the grade option' ), 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'), 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'), 'gradedby' => new external_value(PARAM_RAW, 'The assumed grader of this grading instance'), 'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'), 'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'), ]), 'warnings' => new external_warnings(), ]); } } classes/grades/grader/gradingpanel/scale/external/store.php 0000604 00000014322 15062070555 0020126 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Web service functions relating to scale grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\scale\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use moodle_exception; /** * External grading panel scale API * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), 'notifyuser' => new external_value( PARAM_BOOL, 'Wheteher to notify the user or not', VALUE_DEFAULT, false ), 'formdata' => new external_value( PARAM_RAW, 'The serialised form data representing the grade', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @param string $formdata * @param bool $notifyuser * @return array * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, bool $notifyuser, string $formdata): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); // Validate that this gradeitem is actually enabled. if (!$gradeitem->is_grading_enabled()) { throw new moodle_exception("Grading is not enabled for {$itemname} in this context"); } // Fetch the record for the graded user. $gradeduser = \core_user::get_user($gradeduserid); // Require that this user can save grades. $gradeitem->require_user_can_grade($gradeduser, $USER); if (!$gradeitem->is_using_scale()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales"); } // Parse the serialised string into an object. $data = []; parse_str($formdata, $data); // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); // Notify. if ($notifyuser) { // Send notification. $gradeitem->send_student_notification($gradeduser, $USER); } $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; $maxgrade = (int) $gradeitem->get_grade_item()->grademax; return fetch::get_fetch_data($gradeitem, $gradeduser, $maxgrade, $gradername); } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return fetch::execute_returns(); } } classes/grades/grader/gradingpanel/point/external/fetch.php 0000604 00000017350 15062070555 0020131 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Web service functions relating to point grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\point\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use moodle_exception; use stdClass; /** * External grading panel point API * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class fetch extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @return array * @throws \dml_exception * @throws \invalid_parameter_exception * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); if (!$gradeitem->is_using_direct_grading()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for direct grading"); } // Fetch the actual data. $gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST); // One can access its own grades. Others just if they're graders. if ($gradeduserid != $USER->id) { $gradeitem->require_user_can_grade($gradeduser, $USER); } $hasgrade = $gradeitem->user_has_grade($gradeduser); $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER); $isgrading = $gradeitem->user_can_grade($gradeduser, $USER); // Set up some items we need to return on other interfaces. $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; return self::get_fetch_data($grade, $hasgrade, $gradeitem, $gradername, $isgrading); } /** * Get the data to be fetched. * * @param stdClass $grade * @param bool $hasgrade * @param gradeitem $gradeitem * @param string|null $gradername * @param bool $isgrading * @return array */ public static function get_fetch_data( stdClass $grade, bool $hasgrade, gradeitem $gradeitem, ?string $gradername, bool $isgrading = false ): array { $templatename = 'core_grades/grades/grader/gradingpanel/point'; // We do not want to display anything if we are showing the grade as a letter. For example the 'Grade' might // read 'B-'. We do not want to show the user the actual point they were given. See MDL-71439. if (($gradeitem->get_grade_item()->get_displaytype() == GRADE_DISPLAY_TYPE_LETTER) && !$isgrading) { $templatename = 'core_grades/grades/grader/gradingpanel/point_blank'; } return [ 'templatename' => $templatename, 'hasgrade' => $hasgrade, 'grade' => [ 'grade' => $grade->grade, 'usergrade' => $grade->usergrade, 'maxgrade' => (int) $grade->maxgrade, 'gradedby' => $gradername, 'timecreated' => $grade->timecreated, 'timemodified' => $grade->timemodified, ], 'warnings' => [], ]; } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'), 'hasgrade' => new external_value(PARAM_BOOL, 'Does the user have a grade?'), 'grade' => new external_single_structure([ 'grade' => new external_value(PARAM_FLOAT, 'The numeric grade'), 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'), 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'), 'gradedby' => new external_value(PARAM_RAW, 'The assumed grader of this grading instance'), 'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'), 'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'), ]), 'warnings' => new external_warnings(), ]); } } classes/grades/grader/gradingpanel/point/external/store.php 0000604 00000014700 15062070555 0020170 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Web service functions relating to point grades and grading. * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\grades\grader\gradingpanel\point\external; use coding_exception; use context; use core_grades\component_gradeitem as gradeitem; use core_grades\component_gradeitems; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use moodle_exception; /** * External grading panel point API * * @package core_grades * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store extends external_api { /** * Describes the parameters for fetching the grading panel for a simple grade. * * @return external_function_parameters * @since Moodle 3.8 */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters ([ 'component' => new external_value( PARAM_ALPHANUMEXT, 'The name of the component', VALUE_REQUIRED ), 'contextid' => new external_value( PARAM_INT, 'The ID of the context being graded', VALUE_REQUIRED ), 'itemname' => new external_value( PARAM_ALPHANUM, 'The grade item itemname being graded', VALUE_REQUIRED ), 'gradeduserid' => new external_value( PARAM_INT, 'The ID of the user show', VALUE_REQUIRED ), 'notifyuser' => new external_value( PARAM_BOOL, 'Wheteher to notify the user or not', VALUE_DEFAULT, false ), 'formdata' => new external_value( PARAM_RAW, 'The serialised form data representing the grade', VALUE_REQUIRED ), ]); } /** * Fetch the data required to build a grading panel for a simple grade. * * @param string $component * @param int $contextid * @param string $itemname * @param int $gradeduserid * @param bool $notifyuser * @param string $formdata * @return array * @throws \dml_exception * @throws \invalid_parameter_exception * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, bool $notifyuser, string $formdata): array { global $USER, $CFG; require_once("{$CFG->libdir}/gradelib.php"); [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ] = self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'contextid' => $contextid, 'itemname' => $itemname, 'gradeduserid' => $gradeduserid, 'notifyuser' => $notifyuser, 'formdata' => $formdata, ]); // Validate the context. $context = context::instance_by_id($contextid); self::validate_context($context); // Validate that the supplied itemname is a gradable item. if (!component_gradeitems::is_valid_itemname($component, $itemname)) { throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); } // Fetch the gradeitem instance. $gradeitem = gradeitem::instance($component, $context, $itemname); // Validate that this gradeitem is actually enabled. if (!$gradeitem->is_grading_enabled()) { throw new moodle_exception("Grading is not enabled for {$itemname} in this context"); } // Fetch the record for the graded user. $gradeduser = \core_user::get_user($gradeduserid); // Require that this user can save grades. $gradeitem->require_user_can_grade($gradeduser, $USER); if (!$gradeitem->is_using_direct_grading()) { throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for direct grading"); } // Parse the serialised string into an object. $data = []; parse_str($formdata, $data); // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); $hasgrade = $gradeitem->user_has_grade($gradeduser); // Notify. if ($notifyuser) { // Send notification. $gradeitem->send_student_notification($gradeduser, $USER); } // Fetch the updated grade back out. $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER); $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]); $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null; return fetch::get_fetch_data($grade, $hasgrade, $gradeitem, $gradername); } /** * Describes the data returned from the external function. * * @return external_single_structure * @since Moodle 3.8 */ public static function execute_returns(): external_single_structure { return fetch::execute_returns(); } } classes/component_gradeitem.php 0000604 00000044061 15062070555 0012743 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Compontent definition of a gradeitem. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades; use context; use gradingform_controller; use gradingform_instance; use moodle_exception; use stdClass; use grade_item as core_gradeitem; use grading_manager; /** * Compontent definition of a gradeitem. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class component_gradeitem { /** @var array The scale data for the current grade item */ protected $scale; /** @var string The component */ protected $component; /** @var context The context for this activity */ protected $context; /** @var string The item name */ protected $itemname; /** @var int The grade itemnumber */ protected $itemnumber; /** * component_gradeitem constructor. * * @param string $component * @param context $context * @param string $itemname * @throws \coding_exception */ final protected function __construct(string $component, context $context, string $itemname) { $this->component = $component; $this->context = $context; $this->itemname = $itemname; $this->itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname); } /** * Fetch an instance of a specific component_gradeitem. * * @param string $component * @param context $context * @param string $itemname * @return self */ public static function instance(string $component, context $context, string $itemname): self { $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname); $classname = "{$component}\\grades\\{$itemname}_gradeitem"; if (!class_exists($classname)) { throw new \coding_exception("Unknown gradeitem {$itemname} for component {$classname}"); } return $classname::load_from_context($context); } /** * Load an instance of the current component_gradeitem based on context. * * @param context $context * @return self */ abstract public static function load_from_context(context $context): self; /** * The table name used for grading. * * @return string */ abstract protected function get_table_name(): string; /** * Get the itemid for the current gradeitem. * * @return int */ public function get_grade_itemid(): int { return component_gradeitems::get_itemnumber_from_itemname($this->component, $this->itemname); } /** * Whether grading is enabled for this item. * * @return bool */ abstract public function is_grading_enabled(): bool; /** * Get the grade value for this instance. * The itemname is translated to the relevant grade field for the activity. * * @return int */ abstract protected function get_gradeitem_value(): ?int; /** * Whether the grader can grade the gradee. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @return bool */ abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool; /** * Require that the user can grade, throwing an exception if not. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @throws \required_capability_exception */ abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void; /** * Get the scale if a scale is being used. * * @return stdClass */ protected function get_scale(): ?stdClass { global $DB; $gradetype = $this->get_gradeitem_value(); if ($gradetype > 0) { return null; } // This is a scale. if (null === $this->scale) { $this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]); } return $this->scale; } /** * Check whether a scale is being used for this grade item. * * @return bool */ public function is_using_scale(): bool { $gradetype = $this->get_gradeitem_value(); return $gradetype < 0; } /** * Whether this grade item is configured to use direct grading. * * @return bool */ public function is_using_direct_grading(): bool { if ($this->is_using_scale()) { return false; } if ($this->get_advanced_grading_controller()) { return false; } return true; } /** * Whether this grade item is configured to use advanced grading. * * @return bool */ public function is_using_advanced_grading(): bool { if ($this->is_using_scale()) { return false; } if ($this->get_advanced_grading_controller()) { return true; } return false; } /** * Get the name of the advanced grading method. * * @return string */ public function get_advanced_grading_method(): ?string { $gradingmanager = $this->get_grading_manager(); if (empty($gradingmanager)) { return null; } return $gradingmanager->get_active_method(); } /** * Get the name of the component responsible for grading this gradeitem. * * @return string */ public function get_grading_component_name(): ?string { if (!$this->is_grading_enabled()) { return null; } if ($method = $this->get_advanced_grading_method()) { return "gradingform_{$method}"; } return 'core_grades'; } /** * Get the name of the component subtype responsible for grading this gradeitem. * * @return string */ public function get_grading_component_subtype(): ?string { if (!$this->is_grading_enabled()) { return null; } if ($method = $this->get_advanced_grading_method()) { return null; } if ($this->is_using_scale()) { return 'scale'; } return 'point'; } /** * Whether decimals are allowed. * * @return bool */ protected function allow_decimals(): bool { return $this->get_gradeitem_value() > 0; } /** * Get the grading manager for this advanced grading definition. * * @return grading_manager */ protected function get_grading_manager(): ?grading_manager { require_once(__DIR__ . '/../grading/lib.php'); return get_grading_manager($this->context, $this->component, $this->itemname); } /** * Get the advanced grading controller if advanced grading is enabled. * * @return gradingform_controller */ protected function get_advanced_grading_controller(): ?gradingform_controller { $gradingmanager = $this->get_grading_manager(); if (empty($gradingmanager)) { return null; } if ($gradingmethod = $gradingmanager->get_active_method()) { return $gradingmanager->get_controller($gradingmethod); } return null; } /** * Get the list of available grade items. * * @return array */ public function get_grade_menu(): array { return make_grades_menu($this->get_gradeitem_value()); } /** * Check whether the supplied grade is valid and throw an exception if not. * * @param float $grade The value being checked * @throws moodle_exception * @return bool */ public function check_grade_validity(?float $grade): bool { $grade = grade_floatval(unformat_float($grade)); if ($grade) { if ($this->is_using_scale()) { // Fetch all options for this scale. $scaleoptions = make_menu_from_list($this->get_scale()->scale); if ($grade != -1 && !array_key_exists((int) $grade, $scaleoptions)) { // The selected option did not exist. throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 'maxgrade' => count($scaleoptions), 'grade' => $grade, ]); } } else if ($grade) { $maxgrade = $this->get_gradeitem_value(); if ($grade > $maxgrade) { // The grade is greater than the maximum possible value. throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 'maxgrade' => $maxgrade, 'grade' => $grade, ]); } else if ($grade < 0) { // Negative grades are not supported. throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 'maxgrade' => $maxgrade, 'grade' => $grade, ]); } } } return true; } /** * Create an empty row in the grade for the specified user and grader. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @return stdClass The newly created grade record */ abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass; /** * Get the grade record for the specified grade id. * * @param int $gradeid * @return stdClass * @throws \dml_exception */ public function get_grade(int $gradeid): stdClass { global $DB; return $DB->get_record($this->get_table_name(), ['id' => $gradeid]); } /** * Get the grade for the specified user. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @return stdClass The grade value */ abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass; /** * Returns the grade that should be displayed to the user. * * The grade does not necessarily return a float value, this method takes grade settings into considering so * the correct value be shown, eg. a float vs a letter. * * @param stdClass $gradeduser * @param stdClass $grader * @return stdClass|null */ public function get_formatted_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass { global $DB; if ($grade = $this->get_grade_for_user($gradeduser, $grader)) { $gradeitem = $this->get_grade_item(); if (!$this->is_using_scale()) { $grade->grade = !is_null($grade->grade) ? (float)$grade->grade : null; // Cast non-null values, keeping nulls. $grade->usergrade = grade_format_gradevalue($grade->grade, $gradeitem); $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals()); // If displaying the raw grade, also display the total value. if ($gradeitem->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) { $grade->usergrade .= ' / ' . $grade->maxgrade; } } else { $grade->usergrade = '-'; if ($scale = $DB->get_record('scale', ['id' => $gradeitem->scaleid])) { $options = make_menu_from_list($scale->scale); $gradeint = (int) $grade->grade; if (isset($options[$gradeint])) { $grade->usergrade = $options[$gradeint]; } } $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals()); } return $grade; } return null; } /** * Get the grade status for the specified user. * If the user has a grade as defined by the implementor return true else return false. * * @param stdClass $gradeduser The user being graded * @return bool The grade status */ abstract public function user_has_grade(stdClass $gradeduser): bool; /** * Get grades for all users for the specified gradeitem. * * @return stdClass[] The grades */ abstract public function get_all_grades(): array; /** * Get the grade item instance id. * * This is typically the cmid in the case of an activity, and relates to the iteminstance field in the grade_items * table. * * @return int */ abstract public function get_grade_instance_id(): int; /** * Get the core grade item from the current component grade item. * This is mainly used to access the max grade for a gradeitem * * @return \grade_item The grade item */ public function get_grade_item(): \grade_item { global $CFG; require_once("{$CFG->libdir}/gradelib.php"); [$itemtype, $itemmodule] = \core_component::normalize_component($this->component); $gradeitem = \grade_item::fetch([ 'itemtype' => $itemtype, 'itemmodule' => $itemmodule, 'itemnumber' => $this->itemnumber, 'iteminstance' => $this->get_grade_instance_id(), ]); return $gradeitem; } /** * Create or update the grade. * * @param stdClass $grade * @return bool Success */ abstract protected function store_grade(stdClass $grade): bool; /** * Create or update the grade. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @param stdClass $formdata The data submitted * @return bool Success */ public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool { // Require gradelib for grade_floatval. require_once(__DIR__ . '/../../lib/gradelib.php'); $grade = $this->get_grade_for_user($gradeduser, $grader); if ($this->is_using_advanced_grading()) { $instanceid = $formdata->instanceid; $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid); $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); if ($grade->grade == -1) { // In advanced grading, a value of -1 means no data. return false; } } else { // Handle the case when grade is set to No Grade. if (isset($formdata->grade)) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } return $this->store_grade($grade); } /** * Get the advanced grading instance for the specified grade entry. * * @param stdClass $grader The user who is grading * @param stdClass $grade The row from the grade table. * @param int $instanceid The instanceid of the advanced grading form * @return gradingform_instance */ public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance { $controller = $this->get_advanced_grading_controller($this->itemname); if (empty($controller)) { // Advanced grading not enabeld for this item. return null; } if (!$controller->is_form_available()) { // The form is not available for this item. return null; } // Fetch the instance for the specified graderid/itemid. $gradinginstance = $controller->fetch_instance( (int) $grader->id, (int) $grade->id, $instanceid ); // Set the allowed grade range. $gradinginstance->get_controller()->set_grade_range( $this->get_grade_menu(), $this->allow_decimals() ); return $gradinginstance; } /** * Sends a notification about the item being graded for the student. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading */ public function send_student_notification(stdClass $gradeduser, stdClass $grader): void { $contextname = $this->context->get_context_name(); $eventdata = new \core\message\message(); $eventdata->courseid = $this->context->get_course_context()->instanceid; $eventdata->component = 'moodle'; $eventdata->name = 'gradenotifications'; $eventdata->userfrom = $grader; $eventdata->userto = $gradeduser; $eventdata->subject = get_string('gradenotificationsubject', 'grades'); $eventdata->fullmessage = get_string('gradenotificationmessage', 'grades', $contextname); $eventdata->contexturl = $this->context->get_url(); $eventdata->contexturlname = $contextname; $eventdata->fullmessageformat = FORMAT_HTML; $eventdata->fullmessagehtml = ''; $eventdata->smallmessage = ''; $eventdata->notification = 1; message_send($eventdata); } } classes/privacy/provider.php 0000604 00000003016 15062070555 0012222 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Privacy Subsystem implementation for availability_grade. * * @package availability_grade * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace availability_grade\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for availability_grade implementing null_provider. * * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason() : string { return 'privacy:metadata'; } } classes/privacy/grade_grade_with_history.php 0000604 00000002477 15062070555 0015442 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_grades\privacy; use grade_grade; /** * A grade_item which has a reference to its historical content. * * @package core_grades * @category grade * @copyright 2023 Andrew Lyons <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_grade_with_history extends grade_grade { public int $historyid; public function __construct(\stdClass $params = null, $fetch = true) { // The grade history is not a real grade_grade so we remove the ID. $this->historyid = $params->id; unset($params->id); parent::__construct($params, $fetch); } } classes/component_gradeitems.php 0000604 00000017347 15062070555 0013135 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Helper class to fetch information about component grade items. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades; use code_grades\local\gradeitem\itemnumber_mapping; use code_grades\local\gradeitem\advancedgrading_mapping; /** * Helper class to fetch information about component grade items. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class component_gradeitems { /** * Get the gradeitems classname for the specific component. * * @param string $component The component to fetch the classname for * @return string The composed classname */ protected static function get_component_classname(string $component): string { return "{$component}\\grades\gradeitems"; } /** * Get the grade itemnumber mapping for a component. * * @param string $component The component that the grade item belongs to * @return array */ public static function get_itemname_mapping_for_component(string $component): array { $classname = "{$component}\\grades\gradeitems"; if (!class_exists($classname)) { return [ 0 => '', ]; } if (!is_subclass_of($classname, 'core_grades\local\gradeitem\itemnumber_mapping')) { throw new \coding_exception("The {$classname} class does not implement " . itemnumber_mapping::class); } return $classname::get_itemname_mapping_for_component(); } /** * Whether the named grading item exists. * * @param string $component * @param string $itemname * @return bool */ public static function is_valid_itemname(string $component, string $itemname): bool { $items = self::get_itemname_mapping_for_component($component); return array_search($itemname, $items) !== false; } /** * Check whether the component class defines the advanced grading items. * * @param string $component The component to check * @return bool */ public static function defines_advancedgrading_itemnames_for_component(string $component): bool { return is_subclass_of(self::get_component_classname($component), 'core_grades\local\gradeitem\advancedgrading_mapping'); } /** * Get the list of advanced grading item names for the named component. * * @param string $component * @return array */ public static function get_advancedgrading_itemnames_for_component(string $component): array { $classname = self::get_component_classname($component); if (!self::defines_advancedgrading_itemnames_for_component($component)) { throw new \coding_exception("The {$classname} class does not implement " . advancedgrading_mapping::class); } return $classname::get_advancedgrading_itemnames(); } /** * Whether the named grading item name supports advanced grading. * * @param string $component * @param string $itemname * @return bool */ public static function is_advancedgrading_itemname(string $component, string $itemname): bool { $gradingareas = self::get_advancedgrading_itemnames_for_component($component); return array_search($itemname, $gradingareas) !== false; } /** * Get the suffixed field name for an activity field mapped from its itemnumber. * * For legacy reasons, the first itemnumber has no suffix on field names. * * @param string $component The component that the grade item belongs to * @param int $itemnumber The grade itemnumber * @param string $fieldname The name of the field to be rewritten * @return string The translated field name */ public static function get_field_name_for_itemnumber(string $component, int $itemnumber, string $fieldname): string { $classname = "{$component}\grades\gradeitems"; if (class_exists($classname) && is_subclass_of($classname, 'core_grades\local\gradeitem\fieldname_mapping')) { $fieldname = $classname::get_field_name_for_itemnumber($component, $itemnumber, $fieldname); } else { $itemname = static::get_itemname_from_itemnumber($component, $itemnumber); if ($itemname) { $fieldname .= '_' . $itemname; } } return $fieldname; } /** * Get the suffixed field name for an activity field mapped from its itemnumber. * * For legacy reasons, the first itemnumber has no suffix on field names. * * @param string $component The component that the grade item belongs to * @param string $itemname The grade itemname * @param string $fieldname The name of the field to be rewritten * @return string The translated field name */ public static function get_field_name_for_itemname(string $component, string $itemname, string $fieldname): string { if (empty($itemname)) { return $fieldname; } $itemnumber = static::get_itemnumber_from_itemname($component, $itemname); if ($itemnumber > 0) { return "{$fieldname}_{$itemname}"; } return $fieldname; } /** * Get the itemname for an itemnumber. * * For legacy compatability when the itemnumber is 0, the itemname will always be empty. * * @param string $component The component that the grade item belongs to * @param int $itemnumber The grade itemnumber * @return int The grade itemnumber of the itemname */ public static function get_itemname_from_itemnumber(string $component, int $itemnumber): string { if ($itemnumber === 0) { return ''; } $mappings = self::get_itemname_mapping_for_component($component); if (isset($mappings[$itemnumber])) { return $mappings[$itemnumber]; } if ($itemnumber >= 1000) { // An itemnumber >= 1000 belongs to an outcome. return ''; } throw new \coding_exception("Unknown itemnumber mapping for {$itemnumber} in {$component}"); } /** * Get the itemnumber for a item name. * * For legacy compatability when the itemname is empty, the itemnumber will always be 0. * * @param string $component The component that the grade item belongs to * @param string $itemname The grade itemname * @return int The grade itemname of the itemnumber */ public static function get_itemnumber_from_itemname(string $component, string $itemname): int { if (empty($itemname)) { return 0; } $mappings = self::get_itemname_mapping_for_component($component); $flipped = array_flip($mappings); if (isset($flipped[$itemname])) { return $flipped[$itemname]; } throw new \coding_exception("Unknown itemnumber mapping for {$itemname} in {$component}"); } } classes/local/gradeitem/advancedgrading_mapping.php 0000604 00000002417 15062070555 0016566 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Grade item, itemnumber mapping. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\local\gradeitem; /** * Grade item, itemnumber mapping. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> */ interface advancedgrading_mapping { /** * Get the list of advanced grading item names for this component. * * @return array */ public static function get_advancedgrading_itemnames(): array; } classes/local/gradeitem/itemnumber_mapping.php 0000604 00000002407 15062070555 0015633 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Grade item, itemnumber mapping. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\local\gradeitem; /** * Grade item, itemnumber mapping. * * @package core_grades * @copyright Andrew Nicols <andrew@nicols.co.uk> */ interface itemnumber_mapping { /** * Get the grade item mapping of item number to item name. * * @return array */ public static function get_itemname_mapping_for_component(): array; } classes/local/gradeitem/fieldname_mapping.php 0000604 00000003215 15062070555 0015406 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Grade item, fieldname mapping. * * @package core_grades * @copyright Ilya Tregubov <ilya.a.tregubov@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_grades\local\gradeitem; /** * Grade item, fieldname mapping. * * @package core_grades * @copyright Ilya Tregubov <ilya.a.tregubov@gmail.com> */ interface fieldname_mapping { /** * Get the suffixed field name for an activity field mapped from its itemnumber. * * For legacy reasons, the first itemnumber has no suffix on field names. * * @param string $component The component that the grade item belongs to * @param int $itemnumber The grade itemnumber * @param string $fieldname The name of the field to be rewritten * @return string The translated field name */ public static function get_field_name_for_itemnumber(string $component, int $itemnumber, string $fieldname): string; } lib.php 0000604 00000455446 15062070555 0006046 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Functions used by gradebook plugins and reports. * * @package core_grades * @copyright 2009 Petr Skoda and Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->libdir . '/gradelib.php'); require_once($CFG->dirroot . '/grade/export/lib.php'); use \core_grades\output\action_bar; use \core_grades\output\general_action_bar; /** * This class iterates over all users that are graded in a course. * Returns detailed info about users and their grades. * * @author Petr Skoda <skodak@moodle.org> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class graded_users_iterator { /** * The couse whose users we are interested in */ protected $course; /** * An array of grade items or null if only user data was requested */ protected $grade_items; /** * The group ID we are interested in. 0 means all groups. */ protected $groupid; /** * A recordset of graded users */ protected $users_rs; /** * A recordset of user grades (grade_grade instances) */ protected $grades_rs; /** * Array used when moving to next user while iterating through the grades recordset */ protected $gradestack; /** * The first field of the users table by which the array of users will be sorted */ protected $sortfield1; /** * Should sortfield1 be ASC or DESC */ protected $sortorder1; /** * The second field of the users table by which the array of users will be sorted */ protected $sortfield2; /** * Should sortfield2 be ASC or DESC */ protected $sortorder2; /** * Should users whose enrolment has been suspended be ignored? */ protected $onlyactive = false; /** * Enable user custom fields */ protected $allowusercustomfields = false; /** * List of suspended users in course. This includes users whose enrolment status is suspended * or enrolment has expired or not started. */ protected $suspendedusers = array(); /** * Constructor * * @param object $course A course object * @param array $grade_items array of grade items, if not specified only user info returned * @param int $groupid iterate only group users if present * @param string $sortfield1 The first field of the users table by which the array of users will be sorted * @param string $sortorder1 The order in which the first sorting field will be sorted (ASC or DESC) * @param string $sortfield2 The second field of the users table by which the array of users will be sorted * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC) */ public function __construct($course, $grade_items=null, $groupid=0, $sortfield1='lastname', $sortorder1='ASC', $sortfield2='firstname', $sortorder2='ASC') { $this->course = $course; $this->grade_items = $grade_items; $this->groupid = $groupid; $this->sortfield1 = $sortfield1; $this->sortorder1 = $sortorder1; $this->sortfield2 = $sortfield2; $this->sortorder2 = $sortorder2; $this->gradestack = array(); } /** * Initialise the iterator * * @return boolean success */ public function init() { global $CFG, $DB; $this->close(); export_verify_grades($this->course->id); $course_item = grade_item::fetch_course_item($this->course->id); if ($course_item->needsupdate) { // Can not calculate all final grades - sorry. return false; } $coursecontext = context_course::instance($this->course->id); list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx'); list($gradebookroles_sql, $params) = $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr'); list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, '', 0, $this->onlyactive); $params = array_merge($params, $enrolledparams, $relatedctxparams); if ($this->groupid) { $groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id"; $groupwheresql = "AND gm.groupid = :groupid"; // $params contents: gradebookroles $params['groupid'] = $this->groupid; } else { $groupsql = ""; $groupwheresql = ""; } if (empty($this->sortfield1)) { // We must do some sorting even if not specified. $ofields = ", u.id AS usrt"; $order = "usrt ASC"; } else { $ofields = ", u.$this->sortfield1 AS usrt1"; $order = "usrt1 $this->sortorder1"; if (!empty($this->sortfield2)) { $ofields .= ", u.$this->sortfield2 AS usrt2"; $order .= ", usrt2 $this->sortorder2"; } if ($this->sortfield1 != 'id' and $this->sortfield2 != 'id') { // User order MUST be the same in both queries, // must include the only unique user->id if not already present. $ofields .= ", u.id AS usrt"; $order .= ", usrt ASC"; } } $userfields = 'u.*'; $customfieldssql = ''; if ($this->allowusercustomfields && !empty($CFG->grade_export_customprofilefields)) { $customfieldscount = 0; $customfieldsarray = grade_helper::get_user_profile_fields($this->course->id, $this->allowusercustomfields); foreach ($customfieldsarray as $field) { if (!empty($field->customid)) { $customfieldssql .= " LEFT JOIN (SELECT * FROM {user_info_data} WHERE fieldid = :cf$customfieldscount) cf$customfieldscount ON u.id = cf$customfieldscount.userid"; $userfields .= ", cf$customfieldscount.data AS customfield_{$field->customid}"; $params['cf'.$customfieldscount] = $field->customid; $customfieldscount++; } } } $users_sql = "SELECT $userfields $ofields FROM {user} u JOIN ($enrolledsql) je ON je.id = u.id $groupsql $customfieldssql JOIN ( SELECT DISTINCT ra.userid FROM {role_assignments} ra WHERE ra.roleid $gradebookroles_sql AND ra.contextid $relatedctxsql ) rainner ON rainner.userid = u.id WHERE u.deleted = 0 $groupwheresql ORDER BY $order"; $this->users_rs = $DB->get_recordset_sql($users_sql, $params); if (!$this->onlyactive) { $context = context_course::instance($this->course->id); $this->suspendedusers = get_suspended_userids($context); } else { $this->suspendedusers = array(); } if (!empty($this->grade_items)) { $itemids = array_keys($this->grade_items); list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items'); $params = array_merge($params, $grades_params); $grades_sql = "SELECT g.* $ofields FROM {grade_grades} g JOIN {user} u ON g.userid = u.id JOIN ($enrolledsql) je ON je.id = u.id $groupsql JOIN ( SELECT DISTINCT ra.userid FROM {role_assignments} ra WHERE ra.roleid $gradebookroles_sql AND ra.contextid $relatedctxsql ) rainner ON rainner.userid = u.id WHERE u.deleted = 0 AND g.itemid $itemidsql $groupwheresql ORDER BY $order, g.itemid ASC"; $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params); } else { $this->grades_rs = false; } return true; } /** * Returns information about the next user * @return mixed array of user info, all grades and feedback or null when no more users found */ public function next_user() { if (!$this->users_rs) { return false; // no users present } if (!$this->users_rs->valid()) { if ($current = $this->_pop()) { // this is not good - user or grades updated between the two reads above :-( } return false; // no more users } else { $user = $this->users_rs->current(); $this->users_rs->next(); } // find grades of this user $grade_records = array(); while (true) { if (!$current = $this->_pop()) { break; // no more grades } if (empty($current->userid)) { break; } if ($current->userid != $user->id) { // grade of the next user, we have all for this user $this->_push($current); break; } $grade_records[$current->itemid] = $current; } $grades = array(); $feedbacks = array(); if (!empty($this->grade_items)) { foreach ($this->grade_items as $grade_item) { if (!isset($feedbacks[$grade_item->id])) { $feedbacks[$grade_item->id] = new stdClass(); } if (array_key_exists($grade_item->id, $grade_records)) { $feedbacks[$grade_item->id]->feedback = $grade_records[$grade_item->id]->feedback; $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat; unset($grade_records[$grade_item->id]->feedback); unset($grade_records[$grade_item->id]->feedbackformat); $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false); } else { $feedbacks[$grade_item->id]->feedback = ''; $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE; $grades[$grade_item->id] = new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false); } $grades[$grade_item->id]->grade_item = $grade_item; } } // Set user suspended status. $user->suspendedenrolment = isset($this->suspendedusers[$user->id]); $result = new stdClass(); $result->user = $user; $result->grades = $grades; $result->feedbacks = $feedbacks; return $result; } /** * Close the iterator, do not forget to call this function */ public function close() { if ($this->users_rs) { $this->users_rs->close(); $this->users_rs = null; } if ($this->grades_rs) { $this->grades_rs->close(); $this->grades_rs = null; } $this->gradestack = array(); } /** * Should all enrolled users be exported or just those with an active enrolment? * * @param bool $onlyactive True to limit the export to users with an active enrolment */ public function require_active_enrolment($onlyactive = true) { if (!empty($this->users_rs)) { debugging('Calling require_active_enrolment() has no effect unless you call init() again', DEBUG_DEVELOPER); } $this->onlyactive = $onlyactive; } /** * Allow custom fields to be included * * @param bool $allow Whether to allow custom fields or not * @return void */ public function allow_user_custom_fields($allow = true) { if ($allow) { $this->allowusercustomfields = true; } else { $this->allowusercustomfields = false; } } /** * Add a grade_grade instance to the grade stack * * @param grade_grade $grade Grade object * * @return void */ private function _push($grade) { array_push($this->gradestack, $grade); } /** * Remove a grade_grade instance from the grade stack * * @return grade_grade current grade object */ private function _pop() { global $DB; if (empty($this->gradestack)) { if (empty($this->grades_rs) || !$this->grades_rs->valid()) { return null; // no grades present } $current = $this->grades_rs->current(); $this->grades_rs->next(); return $current; } else { return array_pop($this->gradestack); } } } /** * Print a selection popup form of the graded users in a course. * * @deprecated since 2.0 * * @param int $course id of the course * @param string $actionpage The page receiving the data from the popoup form * @param int $userid id of the currently selected user (or 'all' if they are all selected) * @param int $groupid id of requested group, 0 means all * @param int $includeall bool include all option * @param bool $return If true, will return the HTML, otherwise, will print directly * @return null */ function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) { global $CFG, $USER, $OUTPUT; return $OUTPUT->render(grade_get_graded_users_select(substr($actionpage, 0, strpos($actionpage, '/')), $course, $userid, $groupid, $includeall)); } function grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall) { global $USER, $CFG; if (is_null($userid)) { $userid = $USER->id; } $coursecontext = context_course::instance($course->id); $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); $menu = array(); // Will be a list of userid => user name $menususpendedusers = array(); // Suspended users go to a separate optgroup. $gui = new graded_users_iterator($course, null, $groupid); $gui->require_active_enrolment($showonlyactiveenrol); $gui->init(); $label = get_string('selectauser', 'grades'); if ($includeall) { $menu[0] = get_string('allusers', 'grades'); $label = get_string('selectalloroneuser', 'grades'); } while ($userdata = $gui->next_user()) { $user = $userdata->user; $userfullname = fullname($user); if ($user->suspendedenrolment) { $menususpendedusers[$user->id] = $userfullname; } else { $menu[$user->id] = $userfullname; } } $gui->close(); if ($includeall) { $menu[0] .= " (" . (count($menu) + count($menususpendedusers) - 1) . ")"; } if (!empty($menususpendedusers)) { $menu[] = array(get_string('suspendedusers') => $menususpendedusers); } $gpr = new grade_plugin_return(array('type' => 'report', 'course' => $course, 'groupid' => $groupid)); $select = new single_select( new moodle_url('/grade/report/'.$report.'/index.php', $gpr->get_options()), 'userid', $menu, $userid ); $select->label = $label; $select->formid = 'choosegradeuser'; return $select; } /** * Hide warning about changed grades during upgrade to 2.8. * * @param int $courseid The current course id. */ function hide_natural_aggregation_upgrade_notice($courseid) { unset_config('show_sumofgrades_upgrade_' . $courseid); } /** * Hide warning about changed grades during upgrade from 2.8.0-2.8.6 and 2.9.0. * * @param int $courseid The current course id. */ function grade_hide_min_max_grade_upgrade_notice($courseid) { unset_config('show_min_max_grades_changed_' . $courseid); } /** * Use the grade min and max from the grade_grade. * * This is reserved for core use after an upgrade. * * @param int $courseid The current course id. */ function grade_upgrade_use_min_max_from_grade_grade($courseid) { grade_set_setting($courseid, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_GRADE); grade_force_full_regrading($courseid); // Do this now, because it probably happened to late in the page load to be happen automatically. grade_regrade_final_grades($courseid); } /** * Use the grade min and max from the grade_item. * * This is reserved for core use after an upgrade. * * @param int $courseid The current course id. */ function grade_upgrade_use_min_max_from_grade_item($courseid) { grade_set_setting($courseid, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_ITEM); grade_force_full_regrading($courseid); // Do this now, because it probably happened to late in the page load to be happen automatically. grade_regrade_final_grades($courseid); } /** * Hide warning about changed grades during upgrade to 2.8. * * @param int $courseid The current course id. */ function hide_aggregatesubcats_upgrade_notice($courseid) { unset_config('show_aggregatesubcats_upgrade_' . $courseid); } /** * Hide warning about changed grades due to bug fixes * * @param int $courseid The current course id. */ function hide_gradebook_calculations_freeze_notice($courseid) { unset_config('gradebook_calculations_freeze_' . $courseid); } /** * Print warning about changed grades during upgrade to 2.8. * * @param int $courseid The current course id. * @param context $context The course context. * @param string $thispage The relative path for the current page. E.g. /grade/report/user/index.php * @param boolean $return return as string * * @return nothing or string if $return true */ function print_natural_aggregation_upgrade_notice($courseid, $context, $thispage, $return=false) { global $CFG, $OUTPUT; $html = ''; // Do not do anything if they cannot manage the grades of this course. if (!has_capability('moodle/grade:manage', $context)) { return $html; } $hidesubcatswarning = optional_param('seenaggregatesubcatsupgradedgrades', false, PARAM_BOOL) && confirm_sesskey(); $showsubcatswarning = get_config('core', 'show_aggregatesubcats_upgrade_' . $courseid); $hidenaturalwarning = optional_param('seensumofgradesupgradedgrades', false, PARAM_BOOL) && confirm_sesskey(); $shownaturalwarning = get_config('core', 'show_sumofgrades_upgrade_' . $courseid); $hideminmaxwarning = optional_param('seenminmaxupgradedgrades', false, PARAM_BOOL) && confirm_sesskey(); $showminmaxwarning = get_config('core', 'show_min_max_grades_changed_' . $courseid); $useminmaxfromgradeitem = optional_param('useminmaxfromgradeitem', false, PARAM_BOOL) && confirm_sesskey(); $useminmaxfromgradegrade = optional_param('useminmaxfromgradegrade', false, PARAM_BOOL) && confirm_sesskey(); $minmaxtouse = grade_get_setting($courseid, 'minmaxtouse', $CFG->grade_minmaxtouse); $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid); $acceptgradebookchanges = optional_param('acceptgradebookchanges', false, PARAM_BOOL) && confirm_sesskey(); // Hide the warning if the user told it to go away. if ($hidenaturalwarning) { hide_natural_aggregation_upgrade_notice($courseid); } // Hide the warning if the user told it to go away. if ($hidesubcatswarning) { hide_aggregatesubcats_upgrade_notice($courseid); } // Hide the min/max warning if the user told it to go away. if ($hideminmaxwarning) { grade_hide_min_max_grade_upgrade_notice($courseid); $showminmaxwarning = false; } if ($useminmaxfromgradegrade) { // Revert to the new behaviour, we now use the grade_grade for min/max. grade_upgrade_use_min_max_from_grade_grade($courseid); grade_hide_min_max_grade_upgrade_notice($courseid); $showminmaxwarning = false; } else if ($useminmaxfromgradeitem) { // Apply the new logic, we now use the grade_item for min/max. grade_upgrade_use_min_max_from_grade_item($courseid); grade_hide_min_max_grade_upgrade_notice($courseid); $showminmaxwarning = false; } if (!$hidenaturalwarning && $shownaturalwarning) { $message = get_string('sumofgradesupgradedgrades', 'grades'); $hidemessage = get_string('upgradedgradeshidemessage', 'grades'); $urlparams = array( 'id' => $courseid, 'seensumofgradesupgradedgrades' => true, 'sesskey' => sesskey()); $goawayurl = new moodle_url($thispage, $urlparams); $goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get'); $html .= $OUTPUT->notification($message, 'notifysuccess'); $html .= $goawaybutton; } if (!$hidesubcatswarning && $showsubcatswarning) { $message = get_string('aggregatesubcatsupgradedgrades', 'grades'); $hidemessage = get_string('upgradedgradeshidemessage', 'grades'); $urlparams = array( 'id' => $courseid, 'seenaggregatesubcatsupgradedgrades' => true, 'sesskey' => sesskey()); $goawayurl = new moodle_url($thispage, $urlparams); $goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get'); $html .= $OUTPUT->notification($message, 'notifysuccess'); $html .= $goawaybutton; } if ($showminmaxwarning) { $hidemessage = get_string('upgradedgradeshidemessage', 'grades'); $urlparams = array( 'id' => $courseid, 'seenminmaxupgradedgrades' => true, 'sesskey' => sesskey()); $goawayurl = new moodle_url($thispage, $urlparams); $hideminmaxbutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get'); $moreinfo = html_writer::link(get_docs_url(get_string('minmaxtouse_link', 'grades')), get_string('moreinfo'), array('target' => '_blank')); if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_ITEM) { // Show the message that there were min/max issues that have been resolved. $message = get_string('minmaxupgradedgrades', 'grades') . ' ' . $moreinfo; $revertmessage = get_string('upgradedminmaxrevertmessage', 'grades'); $urlparams = array('id' => $courseid, 'useminmaxfromgradegrade' => true, 'sesskey' => sesskey()); $reverturl = new moodle_url($thispage, $urlparams); $revertbutton = $OUTPUT->single_button($reverturl, $revertmessage, 'get'); $html .= $OUTPUT->notification($message); $html .= $revertbutton . $hideminmaxbutton; } else if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE) { // Show the warning that there are min/max issues that have not be resolved. $message = get_string('minmaxupgradewarning', 'grades') . ' ' . $moreinfo; $fixmessage = get_string('minmaxupgradefixbutton', 'grades'); $urlparams = array('id' => $courseid, 'useminmaxfromgradeitem' => true, 'sesskey' => sesskey()); $fixurl = new moodle_url($thispage, $urlparams); $fixbutton = $OUTPUT->single_button($fixurl, $fixmessage, 'get'); $html .= $OUTPUT->notification($message); $html .= $fixbutton . $hideminmaxbutton; } } if ($gradebookcalculationsfreeze) { if ($acceptgradebookchanges) { // Accept potential changes in grades caused by extra credit bug MDL-49257. hide_gradebook_calculations_freeze_notice($courseid); $courseitem = grade_item::fetch_course_item($courseid); $courseitem->force_regrading(); grade_regrade_final_grades($courseid); $html .= $OUTPUT->notification(get_string('gradebookcalculationsuptodate', 'grades'), 'notifysuccess'); } else { // Show the warning that there may be extra credit weights problems. $a = new stdClass(); $a->gradebookversion = $gradebookcalculationsfreeze; if (preg_match('/(\d{8,})/', $CFG->release, $matches)) { $a->currentversion = $matches[1]; } else { $a->currentversion = $CFG->release; } $a->url = get_docs_url('Gradebook_calculation_changes'); $message = get_string('gradebookcalculationswarning', 'grades', $a); $fixmessage = get_string('gradebookcalculationsfixbutton', 'grades'); $urlparams = array('id' => $courseid, 'acceptgradebookchanges' => true, 'sesskey' => sesskey()); $fixurl = new moodle_url($thispage, $urlparams); $fixbutton = $OUTPUT->single_button($fixurl, $fixmessage, 'get'); $html .= $OUTPUT->notification($message); $html .= $fixbutton; } } if (!empty($html)) { $html = html_writer::tag('div', $html, array('class' => 'core_grades_notices')); } if ($return) { return $html; } else { echo $html; } } /** * grade_get_plugin_info * * @param int $courseid The course id * @param string $active_type type of plugin on current page - import, export, report or edit * @param string $active_plugin active plugin type - grader, user, cvs, ... * * @return array */ function grade_get_plugin_info($courseid, $active_type, $active_plugin) { global $CFG, $SITE; $context = context_course::instance($courseid); $plugin_info = array(); $count = 0; $active = ''; $url_prefix = $CFG->wwwroot . '/grade/'; // Language strings $plugin_info['strings'] = grade_helper::get_plugin_strings(); if ($reports = grade_helper::get_plugins_reports($courseid)) { $plugin_info['report'] = $reports; } if ($settings = grade_helper::get_info_manage_settings($courseid)) { $plugin_info['settings'] = $settings; } if ($scale = grade_helper::get_info_scales($courseid)) { $plugin_info['scale'] = array('view'=>$scale); } if ($outcomes = grade_helper::get_info_outcomes($courseid)) { $plugin_info['outcome'] = $outcomes; } if ($letters = grade_helper::get_info_letters($courseid)) { $plugin_info['letter'] = $letters; } if ($imports = grade_helper::get_plugins_import($courseid)) { $plugin_info['import'] = $imports; } if ($exports = grade_helper::get_plugins_export($courseid)) { $plugin_info['export'] = $exports; } // Let other plugins add plugins here so that we get extra tabs // in the gradebook. $callbacks = get_plugins_with_function('extend_gradebook_plugininfo', 'lib.php'); foreach ($callbacks as $plugins) { foreach ($plugins as $pluginfunction) { $plugin_info = $pluginfunction($plugin_info, $courseid); } } foreach ($plugin_info as $plugin_type => $plugins) { if (!empty($plugins->id) && $active_plugin == $plugins->id) { $plugin_info['strings']['active_plugin_str'] = $plugins->string; break; } foreach ($plugins as $plugin) { if (is_a($plugin, grade_plugin_info::class)) { if ($plugin_type === $active_type && $active_plugin == $plugin->id) { $plugin_info['strings']['active_plugin_str'] = $plugin->string; } } } } return $plugin_info; } /** * Load a valid list of gradable users in a course. * * @param int $courseid The course ID. * @param int|null $groupid The group ID (optional). * @return array $users A list of enrolled gradable users. */ function get_gradable_users(int $courseid, ?int $groupid = null): array { global $CFG; $context = context_course::instance($courseid); // Create a graded_users_iterator because it will properly check the groups etc. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $onlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol) || !has_capability('moodle/course:viewsuspendedusers', $context); $course = get_course($courseid); $gui = new graded_users_iterator($course, null, $groupid); $gui->require_active_enrolment($onlyactiveenrol); $gui->init(); // Flatten the users. $users = []; while ($user = $gui->next_user()) { $users[$user->user->id] = $user->user; } $gui->close(); return $users; } /** * A simple class containing info about grade plugins. * Can be subclassed for special rules * * @package core_grades * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_plugin_info { /** * A unique id for this plugin * * @var mixed */ public $id; /** * A URL to access this plugin * * @var mixed */ public $link; /** * The name of this plugin * * @var mixed */ public $string; /** * Another grade_plugin_info object, parent of the current one * * @var mixed */ public $parent; /** * Constructor * * @param int $id A unique id for this plugin * @param string $link A URL to access this plugin * @param string $string The name of this plugin * @param object $parent Another grade_plugin_info object, parent of the current one * * @return void */ public function __construct($id, $link, $string, $parent=null) { $this->id = $id; $this->link = $link; $this->string = $string; $this->parent = $parent; } } /** * Prints the page headers, breadcrumb trail, page heading, (optional) navigation and for any gradebook page. * All gradebook pages MUST use these functions in favour of the usual print_header(), print_header_simple(), * print_heading() etc. * * @param int $courseid Course id * @param string $active_type The type of the current page (report, settings, * import, export, scales, outcomes, letters) * @param string|null $active_plugin The plugin of the current page (grader, fullview etc...) * @param string|bool $heading The heading of the page. * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function * @param string|bool $buttons Additional buttons to display on the page * @param boolean $shownavigation should the gradebook navigation be shown? * @param string|null $headerhelpidentifier The help string identifier if required. * @param string|null $headerhelpcomponent The component for the help string. * @param stdClass|null $user The user object for use with the user context header. * @param action_bar|null $actionbar The actions bar which will be displayed on the page if $shownavigation is set * to true. If $actionbar is not explicitly defined, the general action bar * (\core_grades\output\general_action_bar) will be used by default. * @param null $unused This parameter has been deprecated since 4.3 and should not be used anymore. * @return string HTML code or nothing if $return == false */ function print_grade_page_head(int $courseid, string $active_type, ?string $active_plugin = null, string|bool $heading = false, bool $return = false, $buttons = false, bool $shownavigation = true, ?string $headerhelpidentifier = null, ?string $headerhelpcomponent = null, ?stdClass $user = null, ?action_bar $actionbar = null, $unused = null) { global $CFG, $OUTPUT, $PAGE, $USER; if ($unused !== null) { debugging('Deprecated argument passed to ' . __FUNCTION__, DEBUG_DEVELOPER); } // Put a warning on all gradebook pages if the course has modules currently scheduled for background deletion. require_once($CFG->dirroot . '/course/lib.php'); if (course_modules_pending_deletion($courseid, true)) { \core\notification::add(get_string('gradesmoduledeletionpendingwarning', 'grades'), \core\output\notification::NOTIFY_WARNING); } if ($active_type === 'preferences') { // In Moodle 2.8 report preferences were moved under 'settings'. Allow backward compatibility for 3rd party grade reports. $active_type = 'settings'; } $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin); // Determine the string of the active plugin. $stractive_type = $plugin_info['strings'][$active_type]; $stractiveplugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading; if ($active_type == 'report') { $PAGE->set_pagelayout('report'); } else { $PAGE->set_pagelayout('admin'); } $coursecontext = context_course::instance($courseid); // Title will be constituted by information starting from the unique identifying information for the page. if (in_array($active_type, ['report', 'settings'])) { $uniquetitle = $stractiveplugin; } else { $uniquetitle = $stractive_type . ': ' . $stractiveplugin; } $titlecomponents = [ $uniquetitle, get_string('grades'), $coursecontext->get_context_name(false), ]; $PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $titlecomponents)); $PAGE->set_heading($PAGE->course->fullname); $PAGE->set_secondary_active_tab('grades'); if ($buttons instanceof single_button) { $buttons = $OUTPUT->render($buttons); } $PAGE->set_button($buttons); if ($courseid != SITEID) { grade_extend_settings($plugin_info, $courseid); } // Set the current report as active in the breadcrumbs. if ($active_plugin !== null && $reportnav = $PAGE->settingsnav->find($active_plugin, navigation_node::TYPE_SETTING)) { $reportnav->make_active(); } $returnval = $OUTPUT->header(); if (!$return) { echo $returnval; } if ($shownavigation) { $renderer = $PAGE->get_renderer('core_grades'); // If the navigation action bar is not explicitly defined, use the general (default) action bar. if (!$actionbar) { $actionbar = new general_action_bar($PAGE->context, $PAGE->url, $active_type, $active_plugin); } if ($return) { $returnval .= $renderer->render_action_bar($actionbar); } else { echo $renderer->render_action_bar($actionbar); } } $output = ''; // Add a help dialogue box if provided. if (isset($headerhelpidentifier) && !empty($heading)) { $output = $OUTPUT->heading_with_help($heading, $headerhelpidentifier, $headerhelpcomponent); } else if (isset($user)) { $renderer = $PAGE->get_renderer('core_grades'); // If the user is viewing their own grade report, no need to show the "Message" // and "Add to contact" buttons in the user heading. $showuserbuttons = $user->id != $USER->id; $output = $renderer->user_heading($user, $courseid, $showuserbuttons); } else if (!empty($heading)) { $output = $OUTPUT->heading($heading); } if ($return) { $returnval .= $output; } else { echo $output; } $returnval .= print_natural_aggregation_upgrade_notice($courseid, $coursecontext, $PAGE->url, $return); if ($return) { return $returnval; } } /** * Utility class used for return tracking when using edit and other forms in grade plugins * * @package core_grades * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_plugin_return { /** * Type of grade plugin (e.g. 'edit', 'report') * * @var string */ public $type; /** * Name of grade plugin (e.g. 'grader', 'overview') * * @var string */ public $plugin; /** * Course id being viewed * * @var int */ public $courseid; /** * Id of user whose information is being viewed/edited * * @var int */ public $userid; /** * Id of group for which information is being viewed/edited * * @var int */ public $groupid; /** * Current page # within output * * @var int */ public $page; /** * Constructor * * @param array $params - associative array with return parameters, if not supplied parameter are taken from _GET or _POST */ public function __construct($params = []) { $this->type = optional_param('gpr_type', null, PARAM_SAFEDIR); $this->plugin = optional_param('gpr_plugin', null, PARAM_PLUGIN); $this->courseid = optional_param('gpr_courseid', null, PARAM_INT); $this->userid = optional_param('gpr_userid', null, PARAM_INT); $this->groupid = optional_param('gpr_groupid', null, PARAM_INT); $this->page = optional_param('gpr_page', null, PARAM_INT); foreach ($params as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } } // Allow course object rather than id to be used to specify course // - avoid unnecessary use of get_course. if (array_key_exists('course', $params)) { $course = $params['course']; $this->courseid = $course->id; } else { $course = null; } // If group has been explicitly set in constructor parameters, // we should respect that. if (!array_key_exists('groupid', $params)) { // Otherwise, 'group' in request parameters is a request for a change. // In that case, or if we have no group at all, we should get groupid from // groups_get_course_group, which will do some housekeeping as well as // give us the correct value. $changegroup = optional_param('group', -1, PARAM_INT); if ($changegroup !== -1 or (empty($this->groupid) and !empty($this->courseid))) { if ($course === null) { $course = get_course($this->courseid); } $this->groupid = groups_get_course_group($course, true); } } } /** * Old syntax of class constructor. Deprecated in PHP7. * * @deprecated since Moodle 3.1 */ public function grade_plugin_return($params = null) { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); self::__construct($params); } /** * Returns return parameters as options array suitable for buttons. * @return array options */ public function get_options() { if (empty($this->type)) { return array(); } $params = array(); if (!empty($this->plugin)) { $params['plugin'] = $this->plugin; } if (!empty($this->courseid)) { $params['id'] = $this->courseid; } if (!empty($this->userid)) { $params['userid'] = $this->userid; } if (!empty($this->groupid)) { $params['group'] = $this->groupid; } if (!empty($this->page)) { $params['page'] = $this->page; } return $params; } /** * Returns return url * * @param string $default default url when params not set * @param array $extras Extra URL parameters * * @return string url */ public function get_return_url($default, $extras=null) { global $CFG; if (empty($this->type) or empty($this->plugin)) { return $default; } $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php'; $glue = '?'; if (!empty($this->courseid)) { $url .= $glue.'id='.$this->courseid; $glue = '&'; } if (!empty($this->userid)) { $url .= $glue.'userid='.$this->userid; $glue = '&'; } if (!empty($this->groupid)) { $url .= $glue.'group='.$this->groupid; $glue = '&'; } if (!empty($this->page)) { $url .= $glue.'page='.$this->page; $glue = '&'; } if (!empty($extras)) { foreach ($extras as $key=>$value) { $url .= $glue.$key.'='.$value; $glue = '&'; } } return $url; } /** * Returns string with hidden return tracking form elements. * @return string */ public function get_form_fields() { if (empty($this->type)) { return ''; } $result = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />'; if (!empty($this->plugin)) { $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />'; } if (!empty($this->courseid)) { $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />'; } if (!empty($this->userid)) { $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />'; } if (!empty($this->groupid)) { $result .= '<input type="hidden" name="gpr_groupid" value="'.$this->groupid.'" />'; } if (!empty($this->page)) { $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />'; } return $result; } /** * Add hidden elements into mform * * @param object &$mform moodle form object * * @return void */ public function add_mform_elements(&$mform) { if (empty($this->type)) { return; } $mform->addElement('hidden', 'gpr_type', $this->type); $mform->setType('gpr_type', PARAM_SAFEDIR); if (!empty($this->plugin)) { $mform->addElement('hidden', 'gpr_plugin', $this->plugin); $mform->setType('gpr_plugin', PARAM_PLUGIN); } if (!empty($this->courseid)) { $mform->addElement('hidden', 'gpr_courseid', $this->courseid); $mform->setType('gpr_courseid', PARAM_INT); } if (!empty($this->userid)) { $mform->addElement('hidden', 'gpr_userid', $this->userid); $mform->setType('gpr_userid', PARAM_INT); } if (!empty($this->groupid)) { $mform->addElement('hidden', 'gpr_groupid', $this->groupid); $mform->setType('gpr_groupid', PARAM_INT); } if (!empty($this->page)) { $mform->addElement('hidden', 'gpr_page', $this->page); $mform->setType('gpr_page', PARAM_INT); } } /** * Add return tracking params into url * * @param moodle_url $url A URL * @return moodle_url with return tracking params */ public function add_url_params(moodle_url $url): moodle_url { if (empty($this->type)) { return $url; } $url->param('gpr_type', $this->type); if (!empty($this->plugin)) { $url->param('gpr_plugin', $this->plugin); } if (!empty($this->courseid)) { $url->param('gpr_courseid' ,$this->courseid); } if (!empty($this->userid)) { $url->param('gpr_userid', $this->userid); } if (!empty($this->groupid)) { $url->param('gpr_groupid', $this->groupid); } if (!empty($this->page)) { $url->param('gpr_page', $this->page); } return $url; } } /** * Function central to gradebook for building and printing the navigation (breadcrumb trail). * * @param string $path The path of the calling script (using __FILE__?) * @param string $pagename The language string to use as the last part of the navigation (non-link) * @param mixed $id Either a plain integer (assuming the key is 'id') or * an array of keys and values (e.g courseid => $courseid, itemid...) * * @return string */ function grade_build_nav($path, $pagename=null, $id=null) { global $CFG, $COURSE, $PAGE; $strgrades = get_string('grades', 'grades'); // Parse the path and build navlinks from its elements $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash $path = substr($path, $dirroot_length); $path = str_replace('\\', '/', $path); $path_elements = explode('/', $path); $path_elements_count = count($path_elements); // First link is always 'grade' $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id))); $link = null; $numberofelements = 3; // Prepare URL params string $linkparams = array(); if (!is_null($id)) { if (is_array($id)) { foreach ($id as $idkey => $idvalue) { $linkparams[$idkey] = $idvalue; } } else { $linkparams['id'] = $id; } } $navlink4 = null; // Remove file extensions from filenames foreach ($path_elements as $key => $filename) { $path_elements[$key] = str_replace('.php', '', $filename); } // Second level links switch ($path_elements[1]) { case 'edit': // No link if ($path_elements[3] != 'index.php') { $numberofelements = 4; } break; case 'import': // No link break; case 'export': // No link break; case 'report': // $id is required for this link. Do not print it if $id isn't given if (!is_null($id)) { $link = new moodle_url('/grade/report/index.php', $linkparams); } if ($path_elements[2] == 'grader') { $numberofelements = 4; } break; default: // If this element isn't among the ones already listed above, it isn't supported, throw an error. debugging("grade_build_nav() doesn't support ". $path_elements[1] . " as the second path element after 'grade'."); return false; } $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link); // Third level links if (empty($pagename)) { $pagename = get_string($path_elements[2], 'grades'); } switch ($numberofelements) { case 3: $PAGE->navbar->add($pagename, $link); break; case 4: if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') { $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams)); } $PAGE->navbar->add($pagename); break; } return ''; } /** * General structure representing grade items in course * * @package core_grades * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_structure { public $context; public $courseid; /** * Reference to modinfo for current course (for performance, to save * retrieving it from courseid every time). Not actually set except for * the grade_tree type. * @var course_modinfo */ public $modinfo; /** * 1D array of grade items only */ public $items; /** * Returns icon of element * * @param array &$element An array representing an element in the grade_tree * @param bool $spacerifnone return spacer if no icon found * * @return string icon or spacer */ public function get_element_icon(&$element, $spacerifnone=false) { global $CFG, $OUTPUT; require_once $CFG->libdir.'/filelib.php'; $outputstr = ''; // Object holding pix_icon information before instantiation. $icon = new stdClass(); $icon->attributes = array( 'class' => 'icon itemicon' ); $icon->component = 'moodle'; $none = true; switch ($element['type']) { case 'item': case 'courseitem': case 'categoryitem': $none = false; $is_course = $element['object']->is_course_item(); $is_category = $element['object']->is_category_item(); $is_scale = $element['object']->gradetype == GRADE_TYPE_SCALE; $is_value = $element['object']->gradetype == GRADE_TYPE_VALUE; $is_outcome = !empty($element['object']->outcomeid); if ($element['object']->is_calculated()) { $icon->pix = 'i/calc'; $icon->title = s(get_string('calculatedgrade', 'grades')); } else if (($is_course or $is_category) and ($is_scale or $is_value)) { if ($category = $element['object']->get_item_category()) { $aggrstrings = grade_helper::get_aggregation_strings(); $stragg = $aggrstrings[$category->aggregation]; $icon->pix = 'i/calc'; $icon->title = s($stragg); switch ($category->aggregation) { case GRADE_AGGREGATE_MEAN: case GRADE_AGGREGATE_MEDIAN: case GRADE_AGGREGATE_WEIGHTED_MEAN: case GRADE_AGGREGATE_WEIGHTED_MEAN2: case GRADE_AGGREGATE_EXTRACREDIT_MEAN: $icon->pix = 'i/agg_mean'; break; case GRADE_AGGREGATE_SUM: $icon->pix = 'i/agg_sum'; break; } } } else if ($element['object']->itemtype == 'mod') { // Prevent outcomes displaying the same icon as the activity they are attached to. if ($is_outcome) { $icon->pix = 'i/outcomes'; $icon->title = s(get_string('outcome', 'grades')); } else { $modinfo = get_fast_modinfo($element['object']->courseid); $module = $element['object']->itemmodule; $instanceid = $element['object']->iteminstance; if (isset($modinfo->instances[$module][$instanceid])) { $icon->url = $modinfo->instances[$module][$instanceid]->get_icon_url(); } else { $icon->pix = 'monologo'; $icon->component = $element['object']->itemmodule; } $icon->title = s(get_string('modulename', $element['object']->itemmodule)); } } else if ($element['object']->itemtype == 'manual') { if ($element['object']->is_outcome_item()) { $icon->pix = 'i/outcomes'; $icon->title = s(get_string('outcome', 'grades')); } else { $icon->pix = 'i/manual_item'; $icon->title = s(get_string('manualitem', 'grades')); } } break; case 'category': $none = false; $icon->pix = 'i/folder'; $icon->title = s(get_string('category', 'grades')); break; } if ($none) { if ($spacerifnone) { $outputstr = $OUTPUT->spacer() . ' '; } } else if (isset($icon->url)) { $outputstr = html_writer::img($icon->url, $icon->title, $icon->attributes); } else { $outputstr = $OUTPUT->pix_icon($icon->pix, $icon->title, $icon->component, $icon->attributes); } return $outputstr; } /** * Returns the string that describes the type of the element. * * @param array $element An array representing an element in the grade_tree * @return string The string that describes the type of the grade element */ public function get_element_type_string(array $element): string { // If the element is a grade category. if ($element['type'] == 'category') { return get_string('category', 'grades'); } // If the element is a grade item. if (in_array($element['type'], ['item', 'courseitem', 'categoryitem'])) { // If calculated grade item. if ($element['object']->is_calculated()) { return get_string('calculatedgrade', 'grades'); } // If aggregated type grade item. if ($element['object']->is_aggregate_item()) { return get_string('aggregation', 'core_grades'); } // If external grade item (module, plugin, etc.). if ($element['object']->is_external_item()) { // If outcome grade item. if ($element['object']->is_outcome_item()) { return get_string('outcome', 'grades'); } return get_string('modulename', $element['object']->itemmodule); } // If manual grade item. if ($element['object']->itemtype == 'manual') { // If outcome grade item. if ($element['object']->is_outcome_item()) { return get_string('outcome', 'grades'); } return get_string('manualitem', 'grades'); } } return ''; } /** * Returns name of element optionally with icon and link * * @param array &$element An array representing an element in the grade_tree * @param bool $withlink Whether or not this header has a link * @param bool $icon Whether or not to display an icon with this header * @param bool $spacerifnone return spacer if no icon found * @param bool $withdescription Show description if defined by this item. * @param bool $fulltotal If the item is a category total, returns $categoryname."total" * instead of "Category total" or "Course total" * @param moodle_url|null $sortlink Link to sort column. * * @return string header */ public function get_element_header(array &$element, bool $withlink = false, bool $icon = true, bool $spacerifnone = false, bool $withdescription = false, bool $fulltotal = false, ?moodle_url $sortlink = null) { $header = ''; if ($icon) { $header .= $this->get_element_icon($element, $spacerifnone); } $title = $element['object']->get_name($fulltotal); $titleunescaped = $element['object']->get_name($fulltotal, false); $header .= $title; if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and $element['type'] != 'courseitem') { return $header; } if ($sortlink) { $url = $sortlink; $header = html_writer::link($url, $header, [ 'title' => $titleunescaped, 'class' => 'gradeitemheader ' ]); } else { if ($withlink && $url = $this->get_activity_link($element)) { $a = new stdClass(); $a->name = get_string('modulename', $element['object']->itemmodule); $a->title = $titleunescaped; $title = get_string('linktoactivity', 'grades', $a); $header = html_writer::link($url, $header, [ 'title' => $title, 'class' => 'gradeitemheader ', ]); } else { $header = html_writer::span($header, 'gradeitemheader ', [ 'title' => $titleunescaped, 'tabindex' => '0' ]); } } if ($withdescription) { $desc = $element['object']->get_description(); if (!empty($desc)) { $header .= '<div class="gradeitemdescription">' . s($desc) . '</div><div class="gradeitemdescriptionfiller"></div>'; } } return $header; } private function get_activity_link($element) { global $CFG; /** @var array static cache of the grade.php file existence flags */ static $hasgradephp = array(); $itemtype = $element['object']->itemtype; $itemmodule = $element['object']->itemmodule; $iteminstance = $element['object']->iteminstance; $itemnumber = $element['object']->itemnumber; // Links only for module items that have valid instance, module and are // called from grade_tree with valid modinfo if ($itemtype != 'mod' || !$iteminstance || !$itemmodule || !$this->modinfo) { return null; } // Get $cm efficiently and with visibility information using modinfo $instances = $this->modinfo->get_instances(); if (empty($instances[$itemmodule][$iteminstance])) { return null; } $cm = $instances[$itemmodule][$iteminstance]; // Do not add link if activity is not visible to the current user if (!$cm->uservisible) { return null; } if (!array_key_exists($itemmodule, $hasgradephp)) { if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) { $hasgradephp[$itemmodule] = true; } else { $hasgradephp[$itemmodule] = false; } } // If module has grade.php, link to that, otherwise view.php if ($hasgradephp[$itemmodule]) { $args = array('id' => $cm->id, 'itemnumber' => $itemnumber); if (isset($element['userid'])) { $args['userid'] = $element['userid']; } return new moodle_url('/mod/' . $itemmodule . '/grade.php', $args); } else { return new moodle_url('/mod/' . $itemmodule . '/view.php', array('id' => $cm->id)); } } /** * Returns URL of a page that is supposed to contain detailed grade analysis * * At the moment, only activity modules are supported. The method generates link * to the module's file grade.php with the parameters id (cmid), itemid, itemnumber, * gradeid and userid. If the grade.php does not exist, null is returned. * * @return moodle_url|null URL or null if unable to construct it */ public function get_grade_analysis_url(grade_grade $grade) { global $CFG; /** @var array static cache of the grade.php file existence flags */ static $hasgradephp = array(); if (empty($grade->grade_item) or !($grade->grade_item instanceof grade_item)) { throw new coding_exception('Passed grade without the associated grade item'); } $item = $grade->grade_item; if (!$item->is_external_item()) { // at the moment, only activity modules are supported return null; } if ($item->itemtype !== 'mod') { throw new coding_exception('Unknown external itemtype: '.$item->itemtype); } if (empty($item->iteminstance) or empty($item->itemmodule) or empty($this->modinfo)) { return null; } if (!array_key_exists($item->itemmodule, $hasgradephp)) { if (file_exists($CFG->dirroot . '/mod/' . $item->itemmodule . '/grade.php')) { $hasgradephp[$item->itemmodule] = true; } else { $hasgradephp[$item->itemmodule] = false; } } if (!$hasgradephp[$item->itemmodule]) { return null; } $instances = $this->modinfo->get_instances(); if (empty($instances[$item->itemmodule][$item->iteminstance])) { return null; } $cm = $instances[$item->itemmodule][$item->iteminstance]; if (!$cm->uservisible) { return null; } $url = new moodle_url('/mod/'.$item->itemmodule.'/grade.php', array( 'id' => $cm->id, 'itemid' => $item->id, 'itemnumber' => $item->itemnumber, 'gradeid' => $grade->id, 'userid' => $grade->userid, )); return $url; } /** * Returns an action icon leading to the grade analysis page * * @param grade_grade $grade * @return string * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. * @todo MDL-77307 This will be deleted in Moodle 4.6. */ public function get_grade_analysis_icon(grade_grade $grade) { global $OUTPUT; debugging('The function get_grade_analysis_icon() is deprecated, please do not use it anymore.', DEBUG_DEVELOPER); $url = $this->get_grade_analysis_url($grade); if (is_null($url)) { return ''; } $title = get_string('gradeanalysis', 'core_grades'); return $OUTPUT->action_icon($url, new pix_icon('t/preview', ''), null, ['title' => $title, 'aria-label' => $title]); } /** * Returns a link leading to the grade analysis page * * @param grade_grade $grade * @return string|null */ public function get_grade_analysis_link(grade_grade $grade): ?string { $url = $this->get_grade_analysis_url($grade); if (is_null($url)) { return null; } $gradeanalysisstring = get_string('gradeanalysis', 'grades'); return html_writer::link($url, $gradeanalysisstring, ['class' => 'dropdown-item', 'aria-label' => $gradeanalysisstring, 'role' => 'menuitem']); } /** * Returns an action menu for the grade. * * @param grade_grade $grade A grade_grade object * @return string */ public function get_grade_action_menu(grade_grade $grade) : string { global $OUTPUT; $menuitems = []; $url = $this->get_grade_analysis_url($grade); if ($url) { $title = get_string('gradeanalysis', 'core_grades'); $menuitems[] = new action_menu_link_secondary($url, null, $title); } if ($menuitems) { $menu = new action_menu($menuitems); $icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions')); $extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center'; $menu->set_menu_trigger($icon, $extraclasses); $menu->set_menu_left(); return $OUTPUT->render($menu); } else { return ''; } } /** * Returns the grade eid - the grade may not exist yet. * * @param grade_grade $grade_grade A grade_grade object * * @return string eid */ public function get_grade_eid($grade_grade) { if (empty($grade_grade->id)) { return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid; } else { return 'g'.$grade_grade->id; } } /** * Returns the grade_item eid * @param grade_item $grade_item A grade_item object * @return string eid */ public function get_item_eid($grade_item) { return 'ig'.$grade_item->id; } /** * Given a grade_tree element, returns an array of parameters * used to build an icon for that element. * * @param array $element An array representing an element in the grade_tree * * @return array */ public function get_params_for_iconstr($element) { $strparams = new stdClass(); $strparams->category = ''; $strparams->itemname = ''; $strparams->itemmodule = ''; if (!method_exists($element['object'], 'get_name')) { return $strparams; } $strparams->itemname = html_to_text($element['object']->get_name()); // If element name is categorytotal, get the name of the parent category if ($strparams->itemname == get_string('categorytotal', 'grades')) { $parent = $element['object']->get_parent_category(); $strparams->category = $parent->get_name() . ' '; } else { $strparams->category = ''; } $strparams->itemmodule = null; if (isset($element['object']->itemmodule)) { $strparams->itemmodule = $element['object']->itemmodule; } return $strparams; } /** * Return a reset icon for the given element. * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @param bool $returnactionmenulink return the instance of action_menu_link instead of string * @return string|action_menu_link * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. * @todo MDL-77307 This will be deleted in Moodle 4.6. */ public function get_reset_icon($element, $gpr, $returnactionmenulink = false) { global $CFG, $OUTPUT; debugging('The function get_reset_icon() is deprecated, please do not use it anymore.', DEBUG_DEVELOPER); // Limit to category items set to use the natural weights aggregation method, and users // with the capability to manage grades. if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM || !has_capability('moodle/grade:manage', $this->context)) { return $returnactionmenulink ? null : ''; } $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element)); $url = new moodle_url('/grade/edit/tree/action.php', array( 'id' => $this->courseid, 'action' => 'resetweights', 'eid' => $element['eid'], 'sesskey' => sesskey(), )); if ($returnactionmenulink) { return new action_menu_link_secondary($gpr->add_url_params($url), new pix_icon('t/reset', $str), get_string('resetweightsshort', 'grades')); } else { return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str)); } } /** * Returns a link to reset weights for the given element. * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @return string|null */ public function get_reset_weights_link(array $element, object $gpr): ?string { // Limit to category items set to use the natural weights aggregation method, and users // with the capability to manage grades. if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM || !has_capability('moodle/grade:manage', $this->context)) { return null; } $title = get_string('resetweightsshort', 'grades'); $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element)); $url = new moodle_url('/grade/edit/tree/action.php', [ 'id' => $this->courseid, 'action' => 'resetweights', 'eid' => $element['eid'], 'sesskey' => sesskey(), ]); $gpr->add_url_params($url); return html_writer::link($url, $title, ['class' => 'dropdown-item', 'aria-label' => $str, 'role' => 'menuitem']); } /** * Returns a link to delete a given element. * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @return string|null */ public function get_delete_link(array $element, object $gpr): ?string { if ($element['type'] == 'item' || ($element['type'] == 'category' && $element['depth'] > 1)) { if (grade_edit_tree::element_deletable($element)) { $deleteconfirmationurl = new moodle_url('index.php', [ 'id' => $this->courseid, 'action' => 'delete', 'confirm' => 1, 'eid' => $element['eid'], 'sesskey' => sesskey(), ]); $gpr->add_url_params($deleteconfirmationurl); $title = get_string('delete'); return html_writer::link( '', $title, [ 'class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem', 'data-modal' => 'confirmation', 'data-modal-title-str' => json_encode(['confirm', 'core']), 'data-modal-content-str' => json_encode([ 'deletecheck', '', $element['object']->get_name() ]), 'data-modal-yes-button-str' => json_encode(['delete', 'core']), 'data-modal-destination' => $deleteconfirmationurl->out(false), ] ); } } return null; } /** * Returns a link to duplicate a given element. * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @return string|null */ public function get_duplicate_link(array $element, object $gpr): ?string { if ($element['type'] == 'item' || ($element['type'] == 'category' && $element['depth'] > 1)) { if (grade_edit_tree::element_duplicatable($element)) { $duplicateparams = []; $duplicateparams['id'] = $this->courseid; $duplicateparams['action'] = 'duplicate'; $duplicateparams['eid'] = $element['eid']; $duplicateparams['sesskey'] = sesskey(); $url = new moodle_url('index.php', $duplicateparams); $title = get_string('duplicate'); $gpr->add_url_params($url); return html_writer::link($url, $title, ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); } } return null; } /** * Return edit icon for give element * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @param bool $returnactionmenulink return the instance of action_menu_link instead of string * @return string|action_menu_link * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. * @todo MDL-77307 This will be deleted in Moodle 4.6. */ public function get_edit_icon($element, $gpr, $returnactionmenulink = false) { global $CFG, $OUTPUT; debugging('The function get_edit_icon() is deprecated, please do not use it anymore.', DEBUG_DEVELOPER); if (!has_capability('moodle/grade:manage', $this->context)) { if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) { // oki - let them override grade } else { return $returnactionmenulink ? null : ''; } } static $strfeedback = null; static $streditgrade = null; if (is_null($streditgrade)) { $streditgrade = get_string('editgrade', 'grades'); $strfeedback = get_string('feedback'); } $strparams = $this->get_params_for_iconstr($element); $object = $element['object']; switch ($element['type']) { case 'item': case 'categoryitem': case 'courseitem': $stredit = get_string('editverbose', 'grades', $strparams); if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) { $url = new moodle_url('/grade/edit/tree/item.php', array('courseid' => $this->courseid, 'id' => $object->id)); } else { $url = new moodle_url('/grade/edit/tree/outcomeitem.php', array('courseid' => $this->courseid, 'id' => $object->id)); } break; case 'category': $stredit = get_string('editverbose', 'grades', $strparams); $url = new moodle_url('/grade/edit/tree/category.php', array('courseid' => $this->courseid, 'id' => $object->id)); break; case 'grade': $stredit = $streditgrade; if (empty($object->id)) { $url = new moodle_url('/grade/edit/tree/grade.php', array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid)); } else { $url = new moodle_url('/grade/edit/tree/grade.php', array('courseid' => $this->courseid, 'id' => $object->id)); } if (!empty($object->feedback)) { $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat))); } break; default: $url = null; } if ($url) { if ($returnactionmenulink) { return new action_menu_link_secondary($gpr->add_url_params($url), new pix_icon('t/edit', $stredit), get_string('editsettings')); } else { return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit)); } } else { return $returnactionmenulink ? null : ''; } } /** * Returns a link leading to the edit grade/grade item/category page * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @return string|null */ public function get_edit_link(array $element, object $gpr): ?string { global $CFG; $url = null; $title = ''; if ((!has_capability('moodle/grade:manage', $this->context) && (!($element['type'] == 'grade') || !has_capability('moodle/grade:edit', $this->context)))) { return null; } $object = $element['object']; if ($element['type'] == 'grade') { if (empty($object->id)) { $url = new moodle_url('/grade/edit/tree/grade.php', ['courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid]); } else { $url = new moodle_url('/grade/edit/tree/grade.php', ['courseid' => $this->courseid, 'id' => $object->id]); } $url = $gpr->add_url_params($url); $title = get_string('editgrade', 'grades'); } else if (($element['type'] == 'item') || ($element['type'] == 'categoryitem') || ($element['type'] == 'courseitem')) { $url = new moodle_url('#'); if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) { return html_writer::link($url, get_string('itemsedit', 'grades'), [ 'class' => 'dropdown-item', 'aria-label' => get_string('itemsedit', 'grades'), 'role' => 'menuitem', 'data-gprplugin' => $gpr->plugin, 'data-courseid' => $this->courseid, 'data-itemid' => $object->id, 'data-trigger' => 'add-item-form' ]); } else if (count(grade_outcome::fetch_all_available($this->courseid)) > 0) { return html_writer::link($url, get_string('itemsedit', 'grades'), [ 'class' => 'dropdown-item', get_string('itemsedit', 'grades'), 'role' => 'menuitem', 'data-gprplugin' => $gpr->plugin, 'data-courseid' => $this->courseid, 'data-itemid' => $object->id, 'data-trigger' => 'add-outcome-form' ]); } } else if ($element['type'] == 'category') { $url = new moodle_url('#'); $title = get_string('categoryedit', 'grades'); return html_writer::link($url, $title, [ 'class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem', 'data-gprplugin' => $gpr->plugin, 'data-courseid' => $this->courseid, 'data-category' => $object->id, 'data-trigger' => 'add-category-form' ]); } return html_writer::link($url, $title, ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); } /** * Returns link to the advanced grading page * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @return string|null */ public function get_advanced_grading_link(array $element, object $gpr): ?string { global $CFG; /** @var array static cache of the grade.php file existence flags */ static $hasgradephp = []; $itemtype = $element['object']->itemtype; $itemmodule = $element['object']->itemmodule; $iteminstance = $element['object']->iteminstance; $itemnumber = $element['object']->itemnumber; // Links only for module items that have valid instance, module and are // called from grade_tree with valid modinfo. if ($itemtype == 'mod' && $iteminstance && $itemmodule && $this->modinfo) { // Get $cm efficiently and with visibility information using modinfo. $instances = $this->modinfo->get_instances(); if (!empty($instances[$itemmodule][$iteminstance])) { $cm = $instances[$itemmodule][$iteminstance]; // Do not add link if activity is not visible to the current user. if ($cm->uservisible) { if (!array_key_exists($itemmodule, $hasgradephp)) { if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) { $hasgradephp[$itemmodule] = true; } else { $hasgradephp[$itemmodule] = false; } } // If module has grade.php, add link to that. if ($hasgradephp[$itemmodule]) { $args = array('id' => $cm->id, 'itemnumber' => $itemnumber); if (isset($element['userid'])) { $args['userid'] = $element['userid']; } $url = new moodle_url('/mod/' . $itemmodule . '/grade.php', $args); $title = get_string('advancedgrading', 'gradereport_grader', $itemmodule); $gpr->add_url_params($url); return html_writer::link($url, $title, ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); } } } } return null; } /** * Return hiding icon for give element * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @param bool $returnactionmenulink return the instance of action_menu_link instead of string * @return string|action_menu_link * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. * @todo MDL-77307 This will be deleted in Moodle 4.6. */ public function get_hiding_icon($element, $gpr, $returnactionmenulink = false) { global $CFG, $OUTPUT; debugging('The function get_hiding_icon() is deprecated, please do not use it anymore.', DEBUG_DEVELOPER); if (!$element['object']->can_control_visibility()) { return $returnactionmenulink ? null : ''; } if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:hide', $this->context)) { return $returnactionmenulink ? null : ''; } $strparams = $this->get_params_for_iconstr($element); $strshow = get_string('showverbose', 'grades', $strparams); $strhide = get_string('hideverbose', 'grades', $strparams); $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid'])); $url = $gpr->add_url_params($url); if ($element['object']->is_hidden()) { $type = 'show'; $tooltip = $strshow; // Change the icon and add a tooltip showing the date if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) { $type = 'hiddenuntil'; $tooltip = get_string('hiddenuntildate', 'grades', userdate($element['object']->get_hidden())); } $url->param('action', 'show'); if ($returnactionmenulink) { $hideicon = new action_menu_link_secondary($url, new pix_icon('t/'.$type, $tooltip), get_string('show')); } else { $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon'))); } } else { $url->param('action', 'hide'); if ($returnactionmenulink) { $hideicon = new action_menu_link_secondary($url, new pix_icon('t/hide', $strhide), get_string('hide')); } else { $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide)); } } return $hideicon; } /** * Returns a link with url to hide/unhide grade/grade item/grade category * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @return string|null */ public function get_hiding_link(array $element, object $gpr): ?string { if (!$element['object']->can_control_visibility() || !has_capability('moodle/grade:manage', $this->context) || !has_capability('moodle/grade:hide', $this->context)) { return null; } $url = new moodle_url('/grade/edit/tree/action.php', ['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]); $url = $gpr->add_url_params($url); if ($element['object']->is_hidden()) { $url->param('action', 'show'); $title = get_string('show'); } else { $url->param('action', 'hide'); $title = get_string('hide'); } $url = html_writer::link($url, $title, ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); if ($element['type'] == 'grade') { $item = $element['object']->grade_item; if ($item->hidden) { $strparamobj = new stdClass(); $strparamobj->itemname = $item->get_name(true, true); $strnonunhideable = get_string('nonunhideableverbose', 'grades', $strparamobj); $url = html_writer::span($title, 'text-muted dropdown-item', ['title' => $strnonunhideable, 'aria-label' => $title, 'role' => 'menuitem']); } } return $url; } /** * Return locking icon for given element * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * * @return string * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. * @todo MDL-77307 This will be deleted in Moodle 4.6. */ public function get_locking_icon($element, $gpr) { global $CFG, $OUTPUT; debugging('The function get_locking_icon() is deprecated, please do not use it anymore.', DEBUG_DEVELOPER); $strparams = $this->get_params_for_iconstr($element); $strunlock = get_string('unlockverbose', 'grades', $strparams); $strlock = get_string('lockverbose', 'grades', $strparams); $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid'])); $url = $gpr->add_url_params($url); // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) { $strparamobj = new stdClass(); $strparamobj->itemname = $element['object']->grade_item->itemname; $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj); $action = html_writer::tag('span', $OUTPUT->pix_icon('t/locked', $strnonunlockable), array('class' => 'action-icon')); } else if ($element['object']->is_locked()) { $type = 'unlock'; $tooltip = $strunlock; // Change the icon and add a tooltip showing the date if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) { $type = 'locktime'; $tooltip = get_string('locktimedate', 'grades', userdate($element['object']->get_locktime())); } if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) { $action = ''; } else { $url->param('action', 'unlock'); $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon'))); } } else { if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) { $action = ''; } else { $url->param('action', 'lock'); $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock)); } } return $action; } /** * Returns link to lock/unlock grade/grade item/grade category * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * * @return string|null */ public function get_locking_link(array $element, object $gpr): ?string { if (has_capability('moodle/grade:manage', $this->context) && isset($element['object'])) { $title = ''; $url = new moodle_url('/grade/edit/tree/action.php', ['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]); $url = $gpr->add_url_params($url); if ($element['type'] == 'category') { // Grade categories themselves cannot be locked. We lock/unlock their grade items. $children = $element['object']->get_children(true); $alllocked = true; foreach ($children as $child) { if (!$child['object']->is_locked()) { $alllocked = false; break; } } if ($alllocked && has_capability('moodle/grade:unlock', $this->context)) { $title = get_string('unlock', 'grades'); $url->param('action', 'unlock'); } else if (!$alllocked && has_capability('moodle/grade:lock', $this->context)) { $title = get_string('lock', 'grades'); $url->param('action', 'lock'); } else { return null; } } else if (($element['type'] == 'grade') && ($element['object']->grade_item->is_locked())) { // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon. $strparamobj = new stdClass(); $strparamobj->itemname = $element['object']->grade_item->get_name(true, true); $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj); $title = get_string('unlock', 'grades'); return html_writer::span($title, 'text-muted dropdown-item', ['title' => $strnonunlockable, 'aria-label' => $title, 'role' => 'menuitem']); } else if ($element['object']->is_locked()) { if (has_capability('moodle/grade:unlock', $this->context)) { $title = get_string('unlock', 'grades'); $url->param('action', 'unlock'); } else { return null; } } else { if (has_capability('moodle/grade:lock', $this->context)) { $title = get_string('lock', 'grades'); $url->param('action', 'lock'); } else { return null; } } return html_writer::link($url, $title, ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); } else { return null; } } /** * Return calculation icon for given element * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * @param bool $returnactionmenulink return the instance of action_menu_link instead of string * @return string|action_menu_link * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. * @todo MDL-77307 This will be deleted in Moodle 4.6. */ public function get_calculation_icon($element, $gpr, $returnactionmenulink = false) { global $CFG, $OUTPUT; debugging('The function get_calculation_icon() is deprecated, please do not use it anymore.', DEBUG_DEVELOPER); if (!has_capability('moodle/grade:manage', $this->context)) { return $returnactionmenulink ? null : ''; } $type = $element['type']; $object = $element['object']; if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') { $strparams = $this->get_params_for_iconstr($element); $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams); $is_scale = $object->gradetype == GRADE_TYPE_SCALE; $is_value = $object->gradetype == GRADE_TYPE_VALUE; // show calculation icon only when calculation possible if (!$object->is_external_item() and ($is_scale or $is_value)) { if ($object->is_calculated()) { $icon = 't/calc'; } else { $icon = 't/calc_off'; } $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id)); $url = $gpr->add_url_params($url); if ($returnactionmenulink) { return new action_menu_link_secondary($url, new pix_icon($icon, $streditcalculation), get_string('editcalculation', 'grades')); } else { return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation)); } } } return $returnactionmenulink ? null : ''; } /** * Returns link to edit calculation for a grade item. * * @param array $element An array representing an element in the grade_tree * @param object $gpr A grade_plugin_return object * * @return string|null */ public function get_edit_calculation_link(array $element, object $gpr): ?string { if (has_capability('moodle/grade:manage', $this->context) && isset($element['object'])) { $object = $element['object']; $isscale = $object->gradetype == GRADE_TYPE_SCALE; $isvalue = $object->gradetype == GRADE_TYPE_VALUE; // Show calculation icon only when calculation possible. if (!$object->is_external_item() && ($isscale || $isvalue)) { $editcalculationstring = get_string('editcalculation', 'grades'); $url = new moodle_url('/grade/edit/tree/calculation.php', ['courseid' => $this->courseid, 'id' => $object->id]); $url = $gpr->add_url_params($url); return html_writer::link($url, $editcalculationstring, ['class' => 'dropdown-item', 'aria-label' => $editcalculationstring, 'role' => 'menuitem']); } } return null; } /** * Sets status icons for the grade. * * @param array $element array with grade item info * @return string|null status icons container HTML */ public function set_grade_status_icons(array $element): ?string { global $OUTPUT; $context = [ 'hidden' => $element['object']->is_hidden(), ]; if ($element['object'] instanceof grade_grade) { $grade = $element['object']; $context['overridden'] = $grade->is_overridden(); $context['excluded'] = $grade->is_excluded(); $context['feedback'] = !empty($grade->feedback) && $grade->load_grade_item()->gradetype != GRADE_TYPE_TEXT; } $context['classes'] = 'grade_icons data-collapse_gradeicons'; if ($element['object'] instanceof grade_category) { $context['classes'] = 'category_grade_icons'; $children = $element['object']->get_children(true); $alllocked = true; foreach ($children as $child) { if (!$child['object']->is_locked()) { $alllocked = false; break; } } if ($alllocked) { $context['locked'] = true; } } else { $context['locked'] = $element['object']->is_locked(); } // Don't even attempt rendering if there is no status to show. if (in_array(true, $context)) { return $OUTPUT->render_from_template('core_grades/status_icons', $context); } else { return null; } } /** * Returns an action menu for the grade. * * @param array $element Array with cell info. * @param string $mode Mode - gradeitem or user * @param grade_plugin_return $gpr * @param moodle_url|null $baseurl * @return string */ public function get_cell_action_menu(array $element, string $mode, grade_plugin_return $gpr, ?moodle_url $baseurl = null): string { global $OUTPUT, $USER; $context = new stdClass(); if ($mode == 'gradeitem' || $mode == 'setup') { $editable = true; if ($element['type'] == 'grade') { $context->datatype = 'grade'; $item = $element['object']->grade_item; if ($item->is_course_item() || $item->is_category_item()) { $editable = (bool)get_config('moodle', 'grade_overridecat');; } if (!empty($USER->editing)) { if ($editable) { $context->editurl = $this->get_edit_link($element, $gpr); } $context->hideurl = $this->get_hiding_link($element, $gpr); $context->lockurl = $this->get_locking_link($element, $gpr); } $context->gradeanalysisurl = $this->get_grade_analysis_link($element['object']); } else if (($element['type'] == 'item') || ($element['type'] == 'categoryitem') || ($element['type'] == 'courseitem') || ($element['type'] == 'userfield')) { $context->datatype = 'item'; if ($element['type'] == 'item') { if ($mode == 'setup') { $context->deleteurl = $this->get_delete_link($element, $gpr); $context->duplicateurl = $this->get_duplicate_link($element, $gpr); } else { $context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context, true); $context->advancedgradingurl = $this->get_advanced_grading_link($element, $gpr); } $context->divider1 = true; } if (($element['type'] == 'item') || (($element['type'] == 'userfield') && ($element['name'] !== 'fullname'))) { $context->divider2 = true; } if (!empty($USER->editing) || $mode == 'setup') { if (($element['type'] == 'userfield') && ($element['name'] !== 'fullname')) { $context->divider2 = true; } else if (($mode !== 'setup') && ($element['type'] !== 'userfield')) { $context->divider1 = true; $context->divider2 = true; } if ($element['type'] == 'item') { $context->editurl = $this->get_edit_link($element, $gpr); } $context->editcalculationurl = $this->get_edit_calculation_link($element, $gpr); if (isset($element['object'])) { $object = $element['object']; if ($object->itemmodule !== 'quiz') { $context->hideurl = $this->get_hiding_link($element, $gpr); } } $context->lockurl = $this->get_locking_link($element, $gpr); } // Sorting item. if ($baseurl) { $sortlink = clone($baseurl); if (isset($element['object']->id)) { $sortlink->param('sortitemid', $element['object']->id); } else if ($element['type'] == 'userfield') { $context->datatype = $element['name']; $sortlink->param('sortitemid', $element['name']); } if (($element['type'] == 'userfield') && ($element['name'] == 'fullname')) { $sortlink->param('sortitemid', 'firstname'); $context->ascendingfirstnameurl = $this->get_sorting_link($sortlink, $gpr); $context->descendingfirstnameurl = $this->get_sorting_link($sortlink, $gpr, 'desc'); $sortlink->param('sortitemid', 'lastname'); $context->ascendinglastnameurl = $this->get_sorting_link($sortlink, $gpr); $context->descendinglastnameurl = $this->get_sorting_link($sortlink, $gpr, 'desc'); } else { $context->ascendingurl = $this->get_sorting_link($sortlink, $gpr); $context->descendingurl = $this->get_sorting_link($sortlink, $gpr, 'desc'); } } if ($mode !== 'setup') { $context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context); } } else if ($element['type'] == 'category') { $context->datatype = 'category'; if ($mode !== 'setup') { $mode = 'category'; $context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context); } else { $context->deleteurl = $this->get_delete_link($element, $gpr); $context->resetweightsurl = $this->get_reset_weights_link($element, $gpr); } if (!empty($USER->editing) || $mode == 'setup') { if ($mode !== 'setup') { $context->divider1 = true; } $context->editurl = $this->get_edit_link($element, $gpr); $context->hideurl = $this->get_hiding_link($element, $gpr); $context->lockurl = $this->get_locking_link($element, $gpr); } } if (isset($element['object'])) { $context->dataid = $element['object']->id; } else if ($element['type'] == 'userfield') { $context->dataid = $element['name']; } if ($element['type'] != 'text' && !empty($element['object']->feedback)) { $viewfeedbackstring = get_string('viewfeedback', 'grades'); $context->viewfeedbackurl = html_writer::link('#', $viewfeedbackstring, ['class' => 'dropdown-item', 'aria-label' => $viewfeedbackstring, 'role' => 'menuitem', 'data-action' => 'feedback', 'data-courseid' => $this->courseid]); } } else if ($mode == 'user') { $context->datatype = 'user'; $context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context, true); $context->dataid = $element['userid']; } // Omit the second divider if there is nothing between it and the first divider. if (!isset($context->ascendingfirstnameurl) && !isset($context->ascendingurl)) { $context->divider2 = false; } if ($mode == 'setup') { $context->databoundary = 'window'; } if (!empty($USER->editing) || isset($context->gradeanalysisurl) || isset($context->gradesonlyurl) || isset($context->aggregatesonlyurl) || isset($context->fullmodeurl) || isset($context->reporturl0) || isset($context->ascendingfirstnameurl) || isset($context->ascendingurl) || isset($context->viewfeedbackurl) || ($mode == 'setup')) { return $OUTPUT->render_from_template('core_grades/cellmenu', $context); } return ''; } /** * Returns link to sort grade item column * * @param moodle_url $sortlink A base link for sorting * @param object $gpr A grade_plugin_return object * @param string $direction Direction od sorting * @return string */ public function get_sorting_link(moodle_url $sortlink, object $gpr, string $direction = 'asc'): string { if ($direction == 'asc') { $title = get_string('asc'); } else { $title = get_string('desc'); } $sortlink->param('sort', $direction); $gpr->add_url_params($sortlink); return html_writer::link($sortlink, $title, ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); } } /** * Flat structure similar to grade tree. * * @uses grade_structure * @package core_grades * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_seq extends grade_structure { /** * 1D array of elements */ public $elements; /** * Constructor, retrieves and stores array of all grade_category and grade_item * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed. * * @param int $courseid The course id * @param bool $category_grade_last category grade item is the last child * @param bool $nooutcomes Whether or not outcomes should be included */ public function __construct($courseid, $category_grade_last=false, $nooutcomes=false) { global $USER, $CFG; $this->courseid = $courseid; $this->context = context_course::instance($courseid); // get course grade tree $top_element = grade_category::fetch_course_tree($courseid, true); $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes); foreach ($this->elements as $key=>$unused) { $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object']; } } /** * Old syntax of class constructor. Deprecated in PHP7. * * @deprecated since Moodle 3.1 */ public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); self::__construct($courseid, $category_grade_last, $nooutcomes); } /** * Static recursive helper - makes the grade_item for category the last children * * @param array &$element The seed of the recursion * @param bool $category_grade_last category grade item is the last child * @param bool $nooutcomes Whether or not outcomes should be included * * @return array */ public function flatten(&$element, $category_grade_last, $nooutcomes) { if (empty($element['children'])) { return array(); } $children = array(); foreach ($element['children'] as $sortorder=>$unused) { if ($nooutcomes and $element['type'] != 'category' and $element['children'][$sortorder]['object']->is_outcome_item()) { continue; } $children[] = $element['children'][$sortorder]; } unset($element['children']); if ($category_grade_last and count($children) > 1 and ( $children[0]['type'] === 'courseitem' or $children[0]['type'] === 'categoryitem' ) ) { $cat_item = array_shift($children); array_push($children, $cat_item); } $result = array(); foreach ($children as $child) { if ($child['type'] == 'category') { $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes); } else { $child['eid'] = 'i'.$child['object']->id; $result[$child['object']->id] = $child; } } return $result; } /** * Parses the array in search of a given eid and returns a element object with * information about the element it has found. * * @param int $eid Gradetree Element ID * * @return object element */ public function locate_element($eid) { // it is a grade - construct a new object if (strpos($eid, 'n') === 0) { if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) { return null; } $itemid = $matches[1]; $userid = $matches[2]; //extra security check - the grade item must be in this tree if (!$item_el = $this->locate_element('ig'.$itemid)) { return null; } // $gradea->id may be null - means does not exist yet $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid)); $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade'); } else if (strpos($eid, 'g') === 0) { $id = (int) substr($eid, 1); if (!$grade = grade_grade::fetch(array('id'=>$id))) { return null; } //extra security check - the grade item must be in this tree if (!$item_el = $this->locate_element('ig'.$grade->itemid)) { return null; } $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade'); } // it is a category or item foreach ($this->elements as $element) { if ($element['eid'] == $eid) { return $element; } } return null; } } /** * This class represents a complete tree of categories, grade_items and final grades, * organises as an array primarily, but which can also be converted to other formats. * It has simple method calls with complex implementations, allowing for easy insertion, * deletion and moving of items and categories within the tree. * * @uses grade_structure * @package core_grades * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grade_tree extends grade_structure { /** * The basic representation of the tree as a hierarchical, 3-tiered array. * @var object $top_element */ public $top_element; /** * 2D array of grade items and categories * @var array $levels */ public $levels; /** * Grade items * @var array $items */ public $items; /** * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed. * * @param int $courseid The Course ID * @param bool $fillers include fillers and colspans, make the levels var "rectangular" * @param bool $category_grade_last category grade item is the last child * @param array $collapsed array of collapsed categories * @param bool $nooutcomes Whether or not outcomes should be included */ public function __construct($courseid, $fillers=true, $category_grade_last=false, $collapsed=null, $nooutcomes=false) { global $USER, $CFG, $COURSE, $DB; $this->courseid = $courseid; $this->levels = array(); $this->context = context_course::instance($courseid); if (!empty($COURSE->id) && $COURSE->id == $this->courseid) { $course = $COURSE; } else { $course = $DB->get_record('course', array('id' => $this->courseid)); } $this->modinfo = get_fast_modinfo($course); // get course grade tree $this->top_element = grade_category::fetch_course_tree($courseid, true); // collapse the categories if requested if (!empty($collapsed)) { grade_tree::category_collapse($this->top_element, $collapsed); } // no otucomes if requested if (!empty($nooutcomes)) { grade_tree::no_outcomes($this->top_element); } // move category item to last position in category if ($category_grade_last) { grade_tree::category_grade_last($this->top_element); } if ($fillers) { // inject fake categories == fillers grade_tree::inject_fillers($this->top_element, 0); // add colspans to categories and fillers grade_tree::inject_colspans($this->top_element); } grade_tree::fill_levels($this->levels, $this->top_element, 0); } /** * Old syntax of class constructor. Deprecated in PHP7. * * @deprecated since Moodle 3.1 */ public function grade_tree($courseid, $fillers=true, $category_grade_last=false, $collapsed=null, $nooutcomes=false) { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); self::__construct($courseid, $fillers, $category_grade_last, $collapsed, $nooutcomes); } /** * Static recursive helper - removes items from collapsed categories * * @param array &$element The seed of the recursion * @param array $collapsed array of collapsed categories * * @return void */ public function category_collapse(&$element, $collapsed) { if ($element['type'] != 'category') { return; } if (empty($element['children']) or count($element['children']) < 2) { return; } if (in_array($element['object']->id, $collapsed['aggregatesonly'])) { $category_item = reset($element['children']); //keep only category item $element['children'] = array(key($element['children'])=>$category_item); } else { if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item reset($element['children']); $first_key = key($element['children']); unset($element['children'][$first_key]); } foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children grade_tree::category_collapse($element['children'][$sortorder], $collapsed); } } } /** * Static recursive helper - removes all outcomes * * @param array &$element The seed of the recursion * * @return void */ public function no_outcomes(&$element) { if ($element['type'] != 'category') { return; } foreach ($element['children'] as $sortorder=>$child) { if ($element['children'][$sortorder]['type'] == 'item' and $element['children'][$sortorder]['object']->is_outcome_item()) { unset($element['children'][$sortorder]); } else if ($element['children'][$sortorder]['type'] == 'category') { grade_tree::no_outcomes($element['children'][$sortorder]); } } } /** * Static recursive helper - makes the grade_item for category the last children * * @param array &$element The seed of the recursion * * @return void */ public function category_grade_last(&$element) { if (empty($element['children'])) { return; } if (count($element['children']) < 2) { return; } $first_item = reset($element['children']); if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') { // the category item might have been already removed $order = key($element['children']); unset($element['children'][$order]); $element['children'][$order] =& $first_item; } foreach ($element['children'] as $sortorder => $child) { grade_tree::category_grade_last($element['children'][$sortorder]); } } /** * Static recursive helper - fills the levels array, useful when accessing tree elements of one level * * @param array &$levels The levels of the grade tree through which to recurse * @param array &$element The seed of the recursion * @param int $depth How deep are we? * @return void */ public function fill_levels(&$levels, &$element, $depth) { if (!array_key_exists($depth, $levels)) { $levels[$depth] = array(); } // prepare unique identifier if ($element['type'] == 'category') { $element['eid'] = 'cg'.$element['object']->id; } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) { $element['eid'] = 'ig'.$element['object']->id; $this->items[$element['object']->id] =& $element['object']; } $levels[$depth][] =& $element; $depth++; if (empty($element['children'])) { return; } $prev = 0; foreach ($element['children'] as $sortorder=>$child) { grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth); $element['children'][$sortorder]['prev'] = $prev; $element['children'][$sortorder]['next'] = 0; if ($prev) { $element['children'][$prev]['next'] = $sortorder; } $prev = $sortorder; } } /** * Determines whether the grade tree item can be displayed. * This is particularly targeted for grade categories that have no total (None) when rendering the grade tree. * It checks if the grade tree item is of type 'category', and makes sure that the category, or at least one of children, * can be output. * * @param array $element The grade category element. * @return bool True if the grade tree item can be displayed. False, otherwise. */ public static function can_output_item($element) { $canoutput = true; if ($element['type'] === 'category') { $object = $element['object']; $category = grade_category::fetch(array('id' => $object->id)); // Category has total, we can output this. if ($category->get_grade_item()->gradetype != GRADE_TYPE_NONE) { return true; } // Category has no total and has no children, no need to output this. if (empty($element['children'])) { return false; } $canoutput = false; // Loop over children and make sure at least one child can be output. foreach ($element['children'] as $child) { $canoutput = self::can_output_item($child); if ($canoutput) { break; } } } return $canoutput; } /** * Static recursive helper - makes full tree (all leafes are at the same level) * * @param array &$element The seed of the recursion * @param int $depth How deep are we? * * @return int */ public function inject_fillers(&$element, $depth) { $depth++; if (empty($element['children'])) { return $depth; } $chdepths = array(); $chids = array_keys($element['children']); $last_child = end($chids); $first_child = reset($chids); foreach ($chids as $chid) { $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth); } arsort($chdepths); $maxdepth = reset($chdepths); foreach ($chdepths as $chid=>$chd) { if ($chd == $maxdepth) { continue; } if (!self::can_output_item($element['children'][$chid])) { continue; } for ($i=0; $i < $maxdepth-$chd; $i++) { if ($chid == $first_child) { $type = 'fillerfirst'; } else if ($chid == $last_child) { $type = 'fillerlast'; } else { $type = 'filler'; } $oldchild =& $element['children'][$chid]; $element['children'][$chid] = array('object'=>'filler', 'type'=>$type, 'eid'=>'', 'depth'=>$element['object']->depth, 'children'=>array($oldchild)); } } return $maxdepth; } /** * Static recursive helper - add colspan information into categories * * @param array &$element The seed of the recursion * * @return int */ public function inject_colspans(&$element) { if (empty($element['children'])) { return 1; } $count = 0; foreach ($element['children'] as $key=>$child) { if (!self::can_output_item($child)) { continue; } $count += grade_tree::inject_colspans($element['children'][$key]); } $element['colspan'] = $count; return $count; } /** * Parses the array in search of a given eid and returns a element object with * information about the element it has found. * @param int $eid Gradetree Element ID * @return object element */ public function locate_element($eid) { // it is a grade - construct a new object if (strpos($eid, 'n') === 0) { if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) { return null; } $itemid = $matches[1]; $userid = $matches[2]; //extra security check - the grade item must be in this tree if (!$item_el = $this->locate_element('ig'.$itemid)) { return null; } // $gradea->id may be null - means does not exist yet $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid)); $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade'); } else if (strpos($eid, 'g') === 0) { $id = (int) substr($eid, 1); if (!$grade = grade_grade::fetch(array('id'=>$id))) { return null; } //extra security check - the grade item must be in this tree if (!$item_el = $this->locate_element('ig'.$grade->itemid)) { return null; } $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade'); } // it is a category or item foreach ($this->levels as $row) { foreach ($row as $element) { if ($element['type'] == 'filler') { continue; } if ($element['eid'] == $eid) { return $element; } } } return null; } /** * Returns a well-formed XML representation of the grade-tree using recursion. * * @param array $root The current element in the recursion. If null, starts at the top of the tree. * @param string $tabs The control character to use for tabs * * @return string $xml */ public function exporttoxml($root=null, $tabs="\t") { $xml = null; $first = false; if (is_null($root)) { $root = $this->top_element; $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n"; $xml .= "<gradetree>\n"; $first = true; } $type = 'undefined'; if (strpos($root['object']->table, 'grade_categories') !== false) { $type = 'category'; } else if (strpos($root['object']->table, 'grade_items') !== false) { $type = 'item'; } else if (strpos($root['object']->table, 'grade_outcomes') !== false) { $type = 'outcome'; } $xml .= "$tabs<element type=\"$type\">\n"; foreach ($root['object'] as $var => $value) { if (!is_object($value) && !is_array($value) && !empty($value)) { $xml .= "$tabs\t<$var>$value</$var>\n"; } } if (!empty($root['children'])) { $xml .= "$tabs\t<children>\n"; foreach ($root['children'] as $sortorder => $child) { $xml .= $this->exportToXML($child, $tabs."\t\t"); } $xml .= "$tabs\t</children>\n"; } $xml .= "$tabs</element>\n"; if ($first) { $xml .= "</gradetree>"; } return $xml; } /** * Returns a JSON representation of the grade-tree using recursion. * * @param array $root The current element in the recursion. If null, starts at the top of the tree. * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy * * @return string */ public function exporttojson($root=null, $tabs="\t") { $json = null; $first = false; if (is_null($root)) { $root = $this->top_element; $first = true; } $name = ''; if (strpos($root['object']->table, 'grade_categories') !== false) { $name = $root['object']->fullname; if ($name == '?') { $name = $root['object']->get_name(); } } else if (strpos($root['object']->table, 'grade_items') !== false) { $name = $root['object']->itemname; } else if (strpos($root['object']->table, 'grade_outcomes') !== false) { $name = $root['object']->itemname; } $json .= "$tabs {\n"; $json .= "$tabs\t \"type\": \"{$root['type']}\",\n"; $json .= "$tabs\t \"name\": \"$name\",\n"; foreach ($root['object'] as $var => $value) { if (!is_object($value) && !is_array($value) && !empty($value)) { $json .= "$tabs\t \"$var\": \"$value\",\n"; } } $json = substr($json, 0, strrpos($json, ',')); if (!empty($root['children'])) { $json .= ",\n$tabs\t\"children\": [\n"; foreach ($root['children'] as $sortorder => $child) { $json .= $this->exportToJSON($child, $tabs."\t\t"); } $json = substr($json, 0, strrpos($json, ',')); $json .= "\n$tabs\t]\n"; } if ($first) { $json .= "\n}"; } else { $json .= "\n$tabs},\n"; } return $json; } /** * Returns the array of levels * * @return array */ public function get_levels() { return $this->levels; } /** * Returns the array of grade items * * @return array */ public function get_items() { return $this->items; } /** * Returns a specific Grade Item * * @param int $itemid The ID of the grade_item object * * @return grade_item */ public function get_item($itemid) { if (array_key_exists($itemid, $this->items)) { return $this->items[$itemid]; } else { return false; } } } /** * Local shortcut function for creating an edit/delete button for a grade_* object. * @param string $type 'edit' or 'delete' * @param int $courseid The Course ID * @param grade_* $object The grade_* object * @return string html */ function grade_button($type, $courseid, $object) { global $CFG, $OUTPUT; if (preg_match('/grade_(.*)/', get_class($object), $matches)) { $objectidstring = $matches[1] . 'id'; } else { throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!'); } $strdelete = get_string('delete'); $stredit = get_string('edit'); if ($type == 'delete') { $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey())); } else if ($type == 'edit') { $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id)); } return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}, '', array('class' => 'iconsmall'))); } /** * This method adds settings to the settings block for the grade system and its * plugins * * @global moodle_page $PAGE */ function grade_extend_settings($plugininfo, $courseid) { global $PAGE; $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER, null, 'gradeadmin'); $strings = array_shift($plugininfo); if ($reports = grade_helper::get_plugins_reports($courseid)) { foreach ($reports as $report) { $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', '')); } } if ($settings = grade_helper::get_info_manage_settings($courseid)) { $settingsnode = $gradenode->add($strings['settings'], null, navigation_node::TYPE_CONTAINER); foreach ($settings as $setting) { $settingsnode->add($setting->string, $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', '')); } } if ($imports = grade_helper::get_plugins_import($courseid)) { $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER); foreach ($imports as $import) { $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', '')); } } if ($exports = grade_helper::get_plugins_export($courseid)) { $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER); foreach ($exports as $export) { $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', '')); } } if ($letters = grade_helper::get_info_letters($courseid)) { $letters = array_shift($letters); $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', '')); } if ($outcomes = grade_helper::get_info_outcomes($courseid)) { $outcomes = array_shift($outcomes); $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', '')); } if ($scales = grade_helper::get_info_scales($courseid)) { $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', '')); } if ($gradenode->contains_active_node()) { // If the gradenode is active include the settings base node (gradeadministration) in // the navbar, typcially this is ignored. $PAGE->navbar->includesettingsbase = true; // If we can get the course admin node make sure it is closed by default // as in this case the gradenode will be opened if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){ $coursenode->make_inactive(); $coursenode->forceopen = false; } } } /** * Grade helper class * * This class provides several helpful functions that work irrespective of any * current state. * * @copyright 2010 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class grade_helper { /** * Cached manage settings info {@see get_info_settings} * @var grade_plugin_info|false */ protected static $managesetting = null; /** * Cached grade report plugins {@see get_plugins_reports} * @var array|false */ protected static $gradereports = null; /** * Cached grade report plugins preferences {@see get_info_scales} * @var array|false */ protected static $gradereportpreferences = null; /** * Cached scale info {@see get_info_scales} * @var grade_plugin_info|false */ protected static $scaleinfo = null; /** * Cached outcome info {@see get_info_outcomes} * @var grade_plugin_info|false */ protected static $outcomeinfo = null; /** * Cached leftter info {@see get_info_letters} * @var grade_plugin_info|false */ protected static $letterinfo = null; /** * Cached grade import plugins {@see get_plugins_import} * @var array|false */ protected static $importplugins = null; /** * Cached grade export plugins {@see get_plugins_export} * @var array|false */ protected static $exportplugins = null; /** * Cached grade plugin strings * @var array */ protected static $pluginstrings = null; /** * Cached grade aggregation strings * @var array */ protected static $aggregationstrings = null; /** * Cached grade tree plugin strings * @var array */ protected static $langstrings = []; /** * First checks the cached language strings, then returns match if found, or uses get_string() * to get it from the DB, caches it then returns it. * * @deprecated since 4.3 * @todo MDL-78780 This will be deleted in Moodle 4.7. * @param string $strcode * @param string|null $section Optional language section * @return string */ public static function get_lang_string(string $strcode, ?string $section = null): string { debugging('grade_helper::get_lang_string() is deprecated, please use' . ' get_string() instead.', DEBUG_DEVELOPER); if (empty(self::$langstrings[$strcode])) { self::$langstrings[$strcode] = get_string($strcode, $section); } return self::$langstrings[$strcode]; } /** * Gets strings commonly used by the describe plugins * * report => get_string('view'), * scale => get_string('scales'), * outcome => get_string('outcomes', 'grades'), * letter => get_string('letters', 'grades'), * export => get_string('export', 'grades'), * import => get_string('import'), * settings => get_string('settings') * * @return array */ public static function get_plugin_strings() { if (self::$pluginstrings === null) { self::$pluginstrings = array( 'report' => get_string('view'), 'scale' => get_string('scales'), 'outcome' => get_string('outcomes', 'grades'), 'letter' => get_string('letters', 'grades'), 'export' => get_string('export', 'grades'), 'import' => get_string('import'), 'settings' => get_string('edittree', 'grades') ); } return self::$pluginstrings; } /** * Gets strings describing the available aggregation methods. * * @return array */ public static function get_aggregation_strings() { if (self::$aggregationstrings === null) { self::$aggregationstrings = array( GRADE_AGGREGATE_MEAN => get_string('aggregatemean', 'grades'), GRADE_AGGREGATE_WEIGHTED_MEAN => get_string('aggregateweightedmean', 'grades'), GRADE_AGGREGATE_WEIGHTED_MEAN2 => get_string('aggregateweightedmean2', 'grades'), GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'), GRADE_AGGREGATE_MEDIAN => get_string('aggregatemedian', 'grades'), GRADE_AGGREGATE_MIN => get_string('aggregatemin', 'grades'), GRADE_AGGREGATE_MAX => get_string('aggregatemax', 'grades'), GRADE_AGGREGATE_MODE => get_string('aggregatemode', 'grades'), GRADE_AGGREGATE_SUM => get_string('aggregatesum', 'grades') ); } return self::$aggregationstrings; } /** * Get grade_plugin_info object for managing settings if the user can * * @param int $courseid * @return grade_plugin_info[] */ public static function get_info_manage_settings($courseid) { if (self::$managesetting !== null) { return self::$managesetting; } $context = context_course::instance($courseid); self::$managesetting = array(); if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) { self::$managesetting['gradebooksetup'] = new grade_plugin_info('setup', new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)), get_string('gradebooksetup', 'grades')); self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings', new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), get_string('coursegradesettings', 'grades')); } if (self::$gradereportpreferences === null) { self::get_plugins_reports($courseid); } if (self::$gradereportpreferences) { self::$managesetting = array_merge(self::$managesetting, self::$gradereportpreferences); } return self::$managesetting; } /** * Returns an array of plugin reports as grade_plugin_info objects * * @param int $courseid * @return array */ public static function get_plugins_reports($courseid) { global $SITE, $CFG; if (self::$gradereports !== null) { return self::$gradereports; } $context = context_course::instance($courseid); $gradereports = array(); $gradepreferences = array(); foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) { //some reports make no sense if we're not within a course if ($courseid==$SITE->id && ($plugin=='grader' || $plugin=='user')) { continue; } // Remove outcomes report if outcomes not enabled. if ($plugin === 'outcomes' && empty($CFG->enableoutcomes)) { continue; } // Remove ones we can't see if (!has_capability('gradereport/'.$plugin.':view', $context)) { continue; } // Singleview doesn't doesn't accomodate for all cap combos yet, so this is hardcoded.. if ($plugin === 'singleview' && !has_all_capabilities(array('moodle/grade:viewall', 'moodle/grade:edit'), $context)) { continue; } $pluginstr = get_string('pluginname', 'gradereport_'.$plugin); $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid)); $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); // Add link to preferences tab if such a page exists if (file_exists($plugindir.'/preferences.php')) { $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id' => $courseid)); $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url, get_string('preferences', 'grades') . ': ' . $pluginstr); } } if (count($gradereports) == 0) { $gradereports = false; $gradepreferences = false; } else if (count($gradepreferences) == 0) { $gradepreferences = false; asort($gradereports); } else { asort($gradereports); asort($gradepreferences); } self::$gradereports = $gradereports; self::$gradereportpreferences = $gradepreferences; return self::$gradereports; } /** * Get information on scales * @param int $courseid * @return grade_plugin_info */ public static function get_info_scales($courseid) { if (self::$scaleinfo !== null) { return self::$scaleinfo; } if (has_capability('moodle/course:managescales', context_course::instance($courseid))) { $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid)); self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view')); } else { self::$scaleinfo = false; } return self::$scaleinfo; } /** * Get information on outcomes * @param int $courseid * @return grade_plugin_info[]|false */ public static function get_info_outcomes($courseid) { global $CFG, $SITE; if (self::$outcomeinfo !== null) { return self::$outcomeinfo; } $context = context_course::instance($courseid); $canmanage = has_capability('moodle/grade:manage', $context); $canupdate = has_capability('moodle/course:update', $context); if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) { $outcomes = array(); if ($canupdate) { if ($courseid!=$SITE->id) { $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid)); $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades')); } $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid)); $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades')); $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid)); $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades')); } else { if ($courseid!=$SITE->id) { $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid)); $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades')); } } self::$outcomeinfo = $outcomes; } else { self::$outcomeinfo = false; } return self::$outcomeinfo; } /** * Get information on letters * @param int $courseid * @return array */ public static function get_info_letters($courseid) { global $SITE; if (self::$letterinfo !== null) { return self::$letterinfo; } $context = context_course::instance($courseid); $canmanage = has_capability('moodle/grade:manage', $context); $canmanageletters = has_capability('moodle/grade:manageletters', $context); if ($canmanage || $canmanageletters) { // Redirect to system context when report is accessed from admin settings MDL-31633 if ($context->instanceid == $SITE->id) { $param = array('edit' => 1); } else { $param = array('edit' => 1,'id' => $context->id); } self::$letterinfo = array( 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')), 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', $param), get_string('edit')) ); } else { self::$letterinfo = false; } return self::$letterinfo; } /** * Get information import plugins * @param int $courseid * @return array */ public static function get_plugins_import($courseid) { global $CFG; if (self::$importplugins !== null) { return self::$importplugins; } $importplugins = array(); $context = context_course::instance($courseid); if (has_capability('moodle/grade:import', $context)) { foreach (core_component::get_plugin_list('gradeimport') as $plugin => $plugindir) { if (!has_capability('gradeimport/'.$plugin.':view', $context)) { continue; } $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin); $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid)); $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); } // Show key manager if grade publishing is enabled and the user has xml publishing capability. // XML is the only grade import plugin that has publishing feature. if ($CFG->gradepublishing && has_capability('gradeimport/xml:publish', $context)) { $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid)); $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades')); } } if (count($importplugins) > 0) { asort($importplugins); self::$importplugins = $importplugins; } else { self::$importplugins = false; } return self::$importplugins; } /** * Get information export plugins * @param int $courseid * @return array */ public static function get_plugins_export($courseid) { global $CFG; if (self::$exportplugins !== null) { return self::$exportplugins; } $context = context_course::instance($courseid); $exportplugins = array(); $canpublishgrades = 0; if (has_capability('moodle/grade:export', $context)) { foreach (core_component::get_plugin_list('gradeexport') as $plugin => $plugindir) { if (!has_capability('gradeexport/'.$plugin.':view', $context)) { continue; } // All the grade export plugins has grade publishing capabilities. if (has_capability('gradeexport/'.$plugin.':publish', $context)) { $canpublishgrades++; } $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin); $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid)); $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); } // Show key manager if grade publishing is enabled and the user has at least one grade publishing capability. if ($CFG->gradepublishing && $canpublishgrades != 0) { $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid)); $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades')); } } if (count($exportplugins) > 0) { asort($exportplugins); self::$exportplugins = $exportplugins; } else { self::$exportplugins = false; } return self::$exportplugins; } /** * Returns the value of a field from a user record * * @param stdClass $user object * @param stdClass $field object * @return string value of the field */ public static function get_user_field_value($user, $field) { if (!empty($field->customid)) { $fieldname = 'customfield_' . $field->customid; if (!empty($user->{$fieldname}) || is_numeric($user->{$fieldname})) { $fieldvalue = $user->{$fieldname}; } else { $fieldvalue = $field->default; } } else { $fieldvalue = $user->{$field->shortname}; } return $fieldvalue; } /** * Returns an array of user profile fields to be included in export * * @param int $courseid * @param bool $includecustomfields * @return array An array of stdClass instances with customid, shortname, datatype, default and fullname fields */ public static function get_user_profile_fields($courseid, $includecustomfields = false) { global $CFG, $DB; // Gets the fields that have to be hidden $hiddenfields = array_map('trim', explode(',', $CFG->hiddenuserfields)); $context = context_course::instance($courseid); $canseehiddenfields = has_capability('moodle/course:viewhiddenuserfields', $context); if ($canseehiddenfields) { $hiddenfields = array(); } $fields = array(); require_once($CFG->dirroot.'/user/lib.php'); // Loads user_get_default_fields() require_once($CFG->dirroot.'/user/profile/lib.php'); // Loads constants, such as PROFILE_VISIBLE_ALL $userdefaultfields = user_get_default_fields(); // Sets the list of profile fields $userprofilefields = array_map('trim', explode(',', $CFG->grade_export_userprofilefields)); if (!empty($userprofilefields)) { foreach ($userprofilefields as $field) { $field = trim($field); if (in_array($field, $hiddenfields) || !in_array($field, $userdefaultfields)) { continue; } $obj = new stdClass(); $obj->customid = 0; $obj->shortname = $field; $obj->fullname = get_string($field); $fields[] = $obj; } } // Sets the list of custom profile fields $customprofilefields = array_map('trim', explode(',', $CFG->grade_export_customprofilefields)); if ($includecustomfields && !empty($customprofilefields)) { $customfields = profile_get_user_fields_with_data(0); foreach ($customfields as $fieldobj) { $field = (object)$fieldobj->get_field_config_for_external(); // Make sure we can display this custom field if (!in_array($field->shortname, $customprofilefields)) { continue; } else if (in_array($field->shortname, $hiddenfields)) { continue; } else if ($field->visible != PROFILE_VISIBLE_ALL && !$canseehiddenfields) { continue; } $obj = new stdClass(); $obj->customid = $field->id; $obj->shortname = $field->shortname; $obj->fullname = format_string($field->name); $obj->datatype = $field->datatype; $obj->default = $field->defaultdata; $fields[] = $obj; } } return $fields; } /** * This helper method gets a snapshot of all the weights for a course. * It is used as a quick method to see if any wieghts have been automatically adjusted. * @param int $courseid * @return array of itemid -> aggregationcoef2 */ public static function fetch_all_natural_weights_for_course($courseid) { global $DB; $result = array(); $records = $DB->get_records('grade_items', array('courseid'=>$courseid), 'id', 'id, aggregationcoef2'); foreach ($records as $record) { $result[$record->id] = $record->aggregationcoef2; } return $result; } /** * Resets all static caches. * * @return void */ public static function reset_caches() { self::$managesetting = null; self::$gradereports = null; self::$gradereportpreferences = null; self::$scaleinfo = null; self::$outcomeinfo = null; self::$letterinfo = null; self::$importplugins = null; self::$exportplugins = null; self::$pluginstrings = null; self::$aggregationstrings = null; } } grading/manage.php 0000604 00000025276 15062070555 0010135 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * A single gradable area management page * * This page alows the user to set the current active method in the given * area, provides access to the plugin editor and allows user to save the * current form as a template or re-use some existing form. * * @package core_grading * @copyright 2011 David Mudrak <david@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require(__DIR__.'/../../config.php'); require_once($CFG->dirroot.'/grade/grading/lib.php'); // identify gradable area by its id $areaid = optional_param('areaid', null, PARAM_INT); // alternatively the context, component and areaname must be provided $contextid = optional_param('contextid', null, PARAM_INT); $component = optional_param('component', null, PARAM_COMPONENT); $area = optional_param('area', null, PARAM_AREA); // keep the caller's URL so that we know where to send the user finally $returnurl = optional_param('returnurl', null, PARAM_LOCALURL); // active method selector $setmethod = optional_param('setmethod', null, PARAM_PLUGIN); // publish the given form definition as a new template in the forms bank $shareform = optional_param('shareform', null, PARAM_INT); // delete the given form definition $deleteform = optional_param('deleteform', null, PARAM_INT); // consider the required action as confirmed $confirmed = optional_param('confirmed', false, PARAM_BOOL); // a message to display, typically a previous action's result $message = optional_param('message', null, PARAM_NOTAGS); if (!is_null($areaid)) { // get manager by id $manager = get_grading_manager($areaid); } else { // get manager by context and component if (is_null($contextid) or is_null($component) or is_null($area)) { throw new coding_exception('The caller script must identify the gradable area.'); } $context = context::instance_by_id($contextid, MUST_EXIST); $manager = get_grading_manager($context, $component, $area); } if ($manager->get_context()->contextlevel < CONTEXT_COURSE) { throw new coding_exception('Unsupported gradable area context level'); } // get the currently active method $method = $manager->get_active_method(); list($context, $course, $cm) = get_context_info_array($manager->get_context()->id); require_login($course, true, $cm); require_capability('moodle/grade:managegradingforms', $context); if (!empty($returnurl)) { $returnurl = new moodle_url($returnurl); } else { $returnurl = null; } $PAGE->set_url($manager->get_management_url($returnurl)); navigation_node::override_active_url($manager->get_management_url()); $PAGE->set_title(get_string('gradingmanagement', 'core_grading')); $PAGE->set_heading(get_string('gradingmanagement', 'core_grading')); // We don't need to show the default header on a management page. $PAGE->activityheader->disable(); $output = $PAGE->get_renderer('core_grading'); // process the eventual change of the active grading method if (!empty($setmethod)) { require_sesskey(); if ($setmethod == 'none') { // here we expect that noone would actually want to call their plugin as 'none' $setmethod = null; } $manager->set_active_method($setmethod); redirect($PAGE->url); } // publish the form as a template if (!empty($shareform)) { require_capability('moodle/grade:sharegradingforms', context_system::instance()); $controller = $manager->get_controller($method); $definition = $controller->get_definition(); if (!$confirmed) { // let the user confirm they understand what they are doing (haha ;-) echo $output->header(); echo $output->confirm(get_string('manageactionshareconfirm', 'core_grading', s($definition->name)), new moodle_url($PAGE->url, array('shareform' => $shareform, 'confirmed' => 1)), $PAGE->url); echo $output->footer(); die(); } else { require_sesskey(); $newareaid = $manager->create_shared_area($method); $targetarea = get_grading_manager($newareaid); $targetcontroller = $targetarea->get_controller($method); $targetcontroller->update_definition($controller->get_definition_copy($targetcontroller)); $DB->set_field('grading_definitions', 'timecopied', time(), array('id' => $definition->id)); redirect(new moodle_url($PAGE->url, array('message' => get_string('manageactionsharedone', 'core_grading')))); } } // delete the form definition if (!empty($deleteform)) { $controller = $manager->get_controller($method); $definition = $controller->get_definition(); if (!$confirmed) { // let the user confirm they understand the consequences (also known as WTF-effect) echo $output->header(); echo $output->confirm(markdown_to_html(get_string('manageactiondeleteconfirm', 'core_grading', array( 'formname' => s($definition->name), 'component' => $manager->get_component_title(), 'area' => $manager->get_area_title()))), new moodle_url($PAGE->url, array('deleteform' => $deleteform, 'confirmed' => 1)), $PAGE->url); echo $output->footer(); die(); } else { require_sesskey(); $controller->delete_definition(); redirect(new moodle_url($PAGE->url, array('message' => get_string('manageactiondeletedone', 'core_grading')))); } } echo $output->header(); if (!empty($message)) { echo $output->management_message($message); } if ($PAGE->has_secondary_navigation()) { echo $output->heading(get_string('gradingmanagement', 'core_grading')); } else { echo $output->heading(get_string('gradingmanagementtitle', 'core_grading', array( 'component' => $manager->get_component_title(), 'area' => $manager->get_area_title()))); } // display the active grading method information and selector echo $output->management_method_selector($manager, $PAGE->url); // get the currently active method's controller if (!empty($method)) { $controller = $manager->get_controller($method); // display relevant actions echo $output->container_start('actions'); if ($controller->is_form_defined()) { $definition = $controller->get_definition(); // icon to edit the form definition echo $output->management_action_icon($controller->get_editor_url($returnurl), get_string('manageactionedit', 'core_grading'), 'b/document-edit'); // icon to delete the current form definition echo $output->management_action_icon(new moodle_url($PAGE->url, array('deleteform' => $definition->id)), get_string('manageactiondelete', 'core_grading'), 'b/edit-delete'); // icon to save the form as a new template if (has_capability('moodle/grade:sharegradingforms', context_system::instance())) { if (empty($definition->copiedfromid)) { $hasoriginal = false; } else { $hasoriginal = $DB->record_exists('grading_definitions', array('id' => $definition->copiedfromid)); } if (!$controller->is_form_available()) { // drafts can not be shared $allowshare = false; } else if (!$hasoriginal) { // was created from scratch or is orphaned if (empty($definition->timecopied)) { // was never shared before $allowshare = true; } else if ($definition->timemodified > $definition->timecopied) { // was modified since last time shared $allowshare = true; } else { // was not modified since last time shared $allowshare = false; } } else { // was created from a template and the template still exists if ($definition->timecreated == $definition->timemodified) { // was not modified since created $allowshare = false; } else if (empty($definition->timecopied)) { // was modified but was not re-shared yet $allowshare = true; } else if ($definition->timemodified > $definition->timecopied) { // was modified since last time re-shared $allowshare = true; } else { // was not modified since last time re-shared $allowshare = false; } } if ($allowshare) { echo $output->management_action_icon(new moodle_url($PAGE->url, array('shareform' => $definition->id)), get_string('manageactionshare', 'core_grading'), 'b/bookmark-new'); } } } else { echo $output->management_action_icon($controller->get_editor_url($returnurl), get_string('manageactionnew', 'core_grading'), 'b/document-new'); $pickurl = new moodle_url('/grade/grading/pick.php', array('targetid' => $controller->get_areaid())); if (!is_null($returnurl)) { $pickurl->param('returnurl', $returnurl->out(false)); } echo $output->management_action_icon($pickurl, get_string('manageactionclone', 'core_grading'), 'b/edit-copy'); } echo $output->container_end(); // display the message if the form is currently not available (if applicable) if ($message = $controller->form_unavailable_notification()) { echo $output->notification($message); } // display the grading form preview if ($controller->is_form_defined()) { if ($definition->status == gradingform_controller::DEFINITION_STATUS_READY) { $tag = html_writer::tag('span', get_string('statusready', 'core_grading'), array('class' => 'status ready')); } else { $tag = html_writer::tag('span', get_string('statusdraft', 'core_grading'), array('class' => 'status draft')); } echo $output->heading(format_string($definition->name) . ' ' . $tag, 3, 'definition-name'); echo $output->box($controller->render_preview($PAGE), 'definition-preview'); } } echo $output->footer(); grading/pick_form.php 0000604 00000003706 15062070555 0010650 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Defines forms used by pick.php * * @package core_grading * @copyright 2011 David Mudrak <david@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot.'/lib/formslib.php'); /** * Allows to search for a specific shared template * * @package core_grading * @copyright 2011 David Mudrak <david@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class grading_search_template_form extends moodleform { /** * Pretty simple search box */ public function definition() { $mform = $this->_form; $mform->addElement('header', 'searchheader', get_string('searchtemplate', 'core_grading')); $mform->addHelpButton('searchheader', 'searchtemplate', 'core_grading'); $mform->addGroup(array( $mform->createElement('checkbox', 'mode', '', get_string('searchownforms', 'core_grading')), $mform->createElement('text', 'needle', '', array('size' => 30)), $mform->createElement('submit', 'submitbutton', get_string('search')), ), 'buttonar', '', array(' '), false); $mform->setType('needle', PARAM_TEXT); $mform->setType('buttonar', PARAM_RAW); } } grading/form/upgrade.txt 0000604 00000001325 15062070555 0011314 0 ustar 00 This files describes API changes in /grade/grading/form/* - Advanced grading methods information provided here is intended especially for developers. === 3.10 === * Removed gradingform_provider. * Removed the following deprecated functions: get_gradingform_export_data delete_gradingform_for_context delete_gradingform_for_userid === 3.6 === * The privacy interface gradingform_provider has been deprecated. Please use gradingform_provider_v2 instead. === 2.5.2 === * Grading methods now can return grade with decimals. See API functions gradingform_controller::set_grade_range() and gradingform_controller::get_allow_grade_decimals(), and also examples in gradingform_rubric_instance::get_grade(). grading/form/rubric/rubriceditor.php 0000604 00000040332 15062070555 0013621 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * File contains definition of class MoodleQuickForm_rubriceditor * * @package gradingform_rubric * @copyright 2011 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once("HTML/QuickForm/input.php"); /** * Form element for handling rubric editor * * The rubric editor is defined as a separate form element. This allows us to render * criteria, levels and buttons using the rubric's own renderer. Also, the required * Javascript library is included, which processes, on the client, buttons needed * for reordering, adding and deleting criteria. * * If Javascript is disabled when one of those special buttons is pressed, the form * element is not validated and, instead of submitting the form, we process button presses. * * @package gradingform_rubric * @copyright 2011 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input { /** @var string help message */ public $_helpbutton = ''; /** @var string|bool stores the result of the last validation: null - undefined, false - no errors, string - error(s) text */ protected $validationerrors = null; /** @var bool if element has already been validated **/ protected $wasvalidated = false; /** @var bool If non-submit (JS) button was pressed: null - unknown, true/false - button was/wasn't pressed */ protected $nonjsbuttonpressed = false; /** @var bool Message to display in front of the editor (that there exist grades on this rubric being edited) */ protected $regradeconfirmation = false; /** * Constructor for rubric editor * * @param string $elementName * @param string $elementLabel * @param array $attributes */ public function __construct($elementName=null, $elementLabel=null, $attributes=null) { parent::__construct($elementName, $elementLabel, $attributes); } /** * Old syntax of class constructor. Deprecated in PHP7. * * @deprecated since Moodle 3.1 */ public function MoodleQuickForm_rubriceditor($elementName=null, $elementLabel=null, $attributes=null) { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); self::__construct($elementName, $elementLabel, $attributes); } /** * get html for help button * * @return string html for help button */ public function getHelpButton() { return $this->_helpbutton; } /** * The renderer will take care itself about different display in normal and frozen states * * @return string */ public function getElementTemplateType() { return 'default'; } /** * Specifies that confirmation about re-grading needs to be added to this rubric editor. * $changelevel is saved in $this->regradeconfirmation and retrieved in toHtml() * * @see gradingform_rubric_controller::update_or_check_rubric() * @param int $changelevel */ public function add_regrade_confirmation($changelevel) { $this->regradeconfirmation = $changelevel; } /** * Returns html string to display this element * * @return string */ public function toHtml() { global $PAGE; $html = $this->_getTabs(); $renderer = $PAGE->get_renderer('gradingform_rubric'); $data = $this->prepare_data(null, $this->wasvalidated); if (!$this->_flagFrozen) { $mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL; $module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js', 'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'), 'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'), array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric') )); $PAGE->requires->js_init_call('M.gradingform_rubriceditor.init', array( array('name' => $this->getName(), 'criteriontemplate' => $renderer->criterion_template($mode, $data['options'], $this->getName()), 'leveltemplate' => $renderer->level_template($mode, $data['options'], $this->getName()) )), true, $module); } else { // Rubric is frozen, no javascript needed if ($this->_persistantFreeze) { $mode = gradingform_rubric_controller::DISPLAY_EDIT_FROZEN; } else { $mode = gradingform_rubric_controller::DISPLAY_PREVIEW; } } if ($this->regradeconfirmation) { if (!isset($data['regrade'])) { $data['regrade'] = 1; } $html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']); } if ($this->validationerrors) { $html .= html_writer::div($renderer->notification($this->validationerrors)); } $html .= $renderer->display_rubric($data['criteria'], $data['options'], $mode, $this->getName()); return $html; } /** * Prepares the data passed in $_POST: * - processes the pressed buttons 'addlevel', 'addcriterion', 'moveup', 'movedown', 'delete' (when JavaScript is disabled) * sets $this->nonjsbuttonpressed to true/false if such button was pressed * - if options not passed (i.e. we create a new rubric) fills the options array with the default values * - if options are passed completes the options array with unchecked checkboxes * - if $withvalidation is set, adds 'error_xxx' attributes to elements that contain errors and creates an error string * and stores it in $this->validationerrors * * @param array $value * @param boolean $withvalidation whether to enable data validation * @return array */ protected function prepare_data($value = null, $withvalidation = false) { if (null === $value) { $value = $this->getValue(); } if ($this->nonjsbuttonpressed === null) { $this->nonjsbuttonpressed = false; } $totalscore = 0; $errors = array(); $return = array('criteria' => array(), 'options' => gradingform_rubric_controller::get_default_options()); if (!isset($value['criteria'])) { $value['criteria'] = array(); $errors['err_nocriteria'] = 1; } // If options are present in $value, replace default values with submitted values if (!empty($value['options'])) { foreach (array_keys($return['options']) as $option) { // special treatment for checkboxes if (!empty($value['options'][$option])) { $return['options'][$option] = $value['options'][$option]; } else { $return['options'][$option] = null; } } } if (is_array($value)) { // for other array keys of $value no special treatmeant neeeded, copy them to return value as is foreach (array_keys($value) as $key) { if ($key != 'options' && $key != 'criteria') { $return[$key] = $value[$key]; } } } // iterate through criteria $lastaction = null; $lastid = null; $overallminscore = $overallmaxscore = 0; foreach ($value['criteria'] as $id => $criterion) { if ($id == 'addcriterion') { $id = $this->get_next_id(array_keys($value['criteria'])); $criterion = array('description' => '', 'levels' => array()); $i = 0; // when adding new criterion copy the number of levels and their scores from the last criterion if (!empty($value['criteria'][$lastid]['levels'])) { foreach ($value['criteria'][$lastid]['levels'] as $lastlevel) { $criterion['levels']['NEWID'.($i++)]['score'] = $lastlevel['score']; } } else { $criterion['levels']['NEWID'.($i++)]['score'] = 0; } // add more levels so there are at least 3 in the new criterion. Increment by 1 the score for each next one for ($i=$i; $i<3; $i++) { $criterion['levels']['NEWID'.$i]['score'] = $criterion['levels']['NEWID'.($i-1)]['score'] + 1; } // set other necessary fields (definition) for the levels in the new criterion foreach (array_keys($criterion['levels']) as $i) { $criterion['levels'][$i]['definition'] = ''; } $this->nonjsbuttonpressed = true; } $levels = array(); $minscore = $maxscore = null; if (array_key_exists('levels', $criterion)) { foreach ($criterion['levels'] as $levelid => $level) { if ($levelid == 'addlevel') { $levelid = $this->get_next_id(array_keys($criterion['levels'])); $level = array( 'definition' => '', 'score' => 0, ); foreach ($criterion['levels'] as $lastlevel) { if (isset($lastlevel['score'])) { $level['score'] = max($level['score'], ceil(unformat_float($lastlevel['score'])) + 1); } } $this->nonjsbuttonpressed = true; } if (!array_key_exists('delete', $level)) { $score = unformat_float($level['score'], true); if ($withvalidation) { if (!strlen(trim($level['definition']))) { $errors['err_nodefinition'] = 1; $level['error_definition'] = true; } if ($score === null || $score === false) { $errors['err_scoreformat'] = 1; $level['error_score'] = true; } } $levels[$levelid] = $level; if ($minscore === null || $score < $minscore) { $minscore = $score; } if ($maxscore === null || $score > $maxscore) { $maxscore = $score; } } else { $this->nonjsbuttonpressed = true; } } } $totalscore += (float)$maxscore; $criterion['levels'] = $levels; if ($withvalidation && !array_key_exists('delete', $criterion)) { if (count($levels)<2) { $errors['err_mintwolevels'] = 1; $criterion['error_levels'] = true; } if (!strlen(trim($criterion['description']))) { $errors['err_nodescription'] = 1; $criterion['error_description'] = true; } $overallmaxscore += $maxscore; $overallminscore += $minscore; } if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') { unset($criterion['moveup']); if ($lastid !== null) { $lastcriterion = $return['criteria'][$lastid]; unset($return['criteria'][$lastid]); $return['criteria'][$id] = $criterion; $return['criteria'][$lastid] = $lastcriterion; } else { $return['criteria'][$id] = $criterion; } $lastaction = null; $lastid = $id; $this->nonjsbuttonpressed = true; } else if (array_key_exists('delete', $criterion)) { $this->nonjsbuttonpressed = true; } else { if (array_key_exists('movedown', $criterion)) { unset($criterion['movedown']); $lastaction = 'movedown'; $this->nonjsbuttonpressed = true; } $return['criteria'][$id] = $criterion; $lastid = $id; } } if ($totalscore <= 0) { $errors['err_totalscore'] = 1; } // add sort order field to criteria $csortorder = 1; foreach (array_keys($return['criteria']) as $id) { $return['criteria'][$id]['sortorder'] = $csortorder++; } // create validation error string (if needed) if ($withvalidation) { if (!$return['options']['lockzeropoints']) { if ($overallminscore == $overallmaxscore) { $errors['err_novariations'] = 1; } } if (count($errors)) { $rv = array(); foreach ($errors as $error => $v) { $rv[] = get_string($error, 'gradingform_rubric'); } $this->validationerrors = join('<br/ >', $rv); } else { $this->validationerrors = false; } $this->wasvalidated = true; } return $return; } /** * Scans array $ids to find the biggest element ! NEWID*, increments it by 1 and returns * * @param array $ids * @return string */ protected function get_next_id($ids) { $maxid = 0; foreach ($ids as $id) { if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) { $maxid = (int)$matches[1]; } } return 'NEWID'.($maxid+1); } /** * Checks if a submit button was pressed which is supposed to be processed on client side by JS * but user seem to have disabled JS in the browser. * (buttons 'add criteria', 'add level', 'move up', 'move down', etc.) * In this case the form containing this element is prevented from being submitted * * @param array $value * @return boolean true if non-submit button was pressed and not processed by JS */ public function non_js_button_pressed($value) { if ($this->nonjsbuttonpressed === null) { $this->prepare_data($value); } return $this->nonjsbuttonpressed; } /** * Validates that rubric has at least one criterion, at least two levels within one criterion, * each level has a valid score, all levels have filled definitions and all criteria * have filled descriptions * * @param array $value * @return string|false error text or false if no errors found */ public function validate($value) { if (!$this->wasvalidated) { $this->prepare_data($value, true); } return $this->validationerrors; } /** * Prepares the data for saving * * @see prepare_data() * @param array $submitValues * @param boolean $assoc * @return array */ public function exportValue(&$submitValues, $assoc = false) { $value = $this->prepare_data($this->_findValue($submitValues)); return $this->_prepareValue($value, $assoc); } } grading/form/rubric/edit_form.php 0000604 00000021417 15062070555 0013077 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * The form used at the rubric editor page is defined here * * @package gradingform_rubric * @copyright 2011 Marina Glancy <marina@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot.'/lib/formslib.php'); require_once(__DIR__.'/rubriceditor.php'); MoodleQuickForm::registerElementType('rubriceditor', $CFG->dirroot.'/grade/grading/form/rubric/rubriceditor.php', 'MoodleQuickForm_rubriceditor'); /** * Defines the rubric edit form * * @package gradingform_rubric * @copyright 2011 Marina Glancy <marina@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class gradingform_rubric_editrubric extends moodleform { /** * Form element definition */ public function definition() { $form = $this->_form; $form->addElement('hidden', 'areaid'); $form->setType('areaid', PARAM_INT); $form->addElement('hidden', 'returnurl'); $form->setType('returnurl', PARAM_LOCALURL); // name $form->addElement('text', 'name', get_string('name', 'gradingform_rubric'), array('size' => 52, 'aria-required' => 'true')); $form->addRule('name', get_string('required'), 'required', null, 'client'); $form->setType('name', PARAM_TEXT); // description $options = gradingform_rubric_controller::description_form_field_options($this->_customdata['context']); $form->addElement('editor', 'description_editor', get_string('description', 'gradingform_rubric'), null, $options); $form->setType('description_editor', PARAM_RAW); // rubric completion status $choices = array(); $choices[gradingform_controller::DEFINITION_STATUS_DRAFT] = html_writer::tag('span', get_string('statusdraft', 'core_grading'), array('class' => 'status draft')); $choices[gradingform_controller::DEFINITION_STATUS_READY] = html_writer::tag('span', get_string('statusready', 'core_grading'), array('class' => 'status ready')); $form->addElement('select', 'status', get_string('rubricstatus', 'gradingform_rubric'), $choices)->freeze(); // rubric editor $form->addElement('rubriceditor', 'rubric', get_string('rubric', 'gradingform_rubric')); $form->setType('rubric', PARAM_RAW); $buttonarray = array(); $buttonarray[] = &$form->createElement('submit', 'saverubric', get_string('saverubric', 'gradingform_rubric')); if ($this->_customdata['allowdraft']) { $buttonarray[] = &$form->createElement('submit', 'saverubricdraft', get_string('saverubricdraft', 'gradingform_rubric')); } $editbutton = &$form->createElement('submit', 'editrubric', ' '); $editbutton->freeze(); $buttonarray[] = &$editbutton; $buttonarray[] = &$form->createElement('cancel'); $form->addGroup($buttonarray, 'buttonar', '', array(' '), false); $form->closeHeaderBefore('buttonar'); } /** * Setup the form depending on current values. This method is called after definition(), * data submission and set_data(). * All form setup that is dependent on form values should go in here. * * We remove the element status if there is no current status (i.e. rubric is only being created) * so the users do not get confused */ public function definition_after_data() { $form = $this->_form; $el = $form->getElement('status'); if (!$el->getValue()) { $form->removeElement('status'); } else { $vals = array_values($el->getValue()); if ($vals[0] == gradingform_controller::DEFINITION_STATUS_READY) { $this->findButton('saverubric')->setValue(get_string('save', 'gradingform_rubric')); } } } /** * Form vlidation. * If there are errors return array of errors ("fieldname"=>"error message"), * otherwise true if ok. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files) { $err = parent::validation($data, $files); $err = array(); $form = $this->_form; $rubricel = $form->getElement('rubric'); if ($rubricel->non_js_button_pressed($data['rubric'])) { // if JS is disabled and button such as 'Add criterion' is pressed - prevent from submit $err['rubricdummy'] = 1; } else if (isset($data['editrubric'])) { // continue editing $err['rubricdummy'] = 1; } else if (isset($data['saverubric']) && $data['saverubric']) { // If user attempts to make rubric active - it needs to be validated if ($rubricel->validate($data['rubric']) !== false) { $err['rubricdummy'] = 1; } } return $err; } /** * Return submitted data if properly submitted or returns NULL if validation fails or * if there is no submitted data. * * @return object submitted data; NULL if not valid or not submitted or cancelled */ public function get_data() { $data = parent::get_data(); if (!empty($data->saverubric)) { $data->status = gradingform_controller::DEFINITION_STATUS_READY; } else if (!empty($data->saverubricdraft)) { $data->status = gradingform_controller::DEFINITION_STATUS_DRAFT; } return $data; } /** * Check if there are changes in the rubric and it is needed to ask user whether to * mark the current grades for re-grading. User may confirm re-grading and continue, * return to editing or cancel the changes * * @param gradingform_rubric_controller $controller */ public function need_confirm_regrading($controller) { $data = $this->get_data(); if (isset($data->rubric['regrade'])) { // we have already displayed the confirmation on the previous step return false; } if (!isset($data->saverubric) || !$data->saverubric) { // we only need confirmation when button 'Save rubric' is pressed return false; } if (!$controller->has_active_instances()) { // nothing to re-grade, confirmation not needed return false; } $changelevel = $controller->update_or_check_rubric($data); if ($changelevel == 0) { // no changes in the rubric, no confirmation needed return false; } // freeze form elements and pass the values in hidden fields // TODO MDL-29421 description_editor does not freeze the normal way, uncomment below when fixed $form = $this->_form; foreach (array('rubric', 'name'/*, 'description_editor'*/) as $fieldname) { $el =& $form->getElement($fieldname); $el->freeze(); $el->setPersistantFreeze(true); if ($fieldname == 'rubric') { $el->add_regrade_confirmation($changelevel); } } // replace button text 'saverubric' and unfreeze 'Back to edit' button $this->findButton('saverubric')->setValue(get_string('continue')); $el =& $this->findButton('editrubric'); $el->setValue(get_string('backtoediting', 'gradingform_rubric')); $el->unfreeze(); return true; } /** * Returns a form element (submit button) with the name $elementname * * @param string $elementname * @return HTML_QuickForm_element */ protected function &findButton($elementname) { $form = $this->_form; $buttonar =& $form->getElement('buttonar'); $elements =& $buttonar->getElements(); foreach ($elements as $el) { if ($el->getName() == $elementname) { return $el; } } return null; } } grading/form/rubric/pix/icon.png 0000604 00000000223 15062070555 0012644 0 ustar 00 �PNG IHDR �a bKGD � � ����� HIDAT8�� �0/�,��T�h� ���R��$k�0y��0�� �Rkw_y���]��� /�@6��̉;�W=�K IEND�B`� grading/form/rubric/pix/icon.svg 0000604 00000001006 15062070555 0012657 0 ustar 00 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/"> ]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M15 0H1C.5 0 0 .5 0 1v14c0 .5.5 1 1 1h14c.5 0 1-.5 1-1V1c0-.5-.5-1-1-1zM8 14H2v-2h6v2zm0-3H2V9h6v2zm0-3H2V6h6v2zm3 6H9v-2h2v2zm0-3H9V9h2v2zm0-3H9V6h2v2zm3 6h-2v-2h2v2zm0-3h-2V9h2v2zm0-3h-2V6h2v2z" fill="#888"/></svg>