*/
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.
*
$gradeduser = \core_user::get_user($gradeduserid);
$hasgrade = $gradeitem->user_has_grade($gradeduser);
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
+ $maxgrade = (int) $gradeitem->get_grade_item()->grademax;
- return self::get_fetch_data($grade, $hasgrade);
+ return self::get_fetch_data($grade, $hasgrade, $maxgrade);
}
/**
*
* @param stdClass $grade
* @param bool $hasgrade
+ * @param int $maxgrade
* @return array
*/
- public static function get_fetch_data(stdClass $grade, bool $hasgrade): array {
+ public static function get_fetch_data(stdClass $grade, bool $hasgrade, int $maxgrade): array {
return [
'templatename' => 'core_grades/grades/grader/gradingpanel/point',
'hasgrade' => $hasgrade,
'grade' => [
'grade' => $grade->grade,
+ 'usergrade' => $grade->grade,
+ 'maxgrade' => $maxgrade,
'timecreated' => $grade->timecreated,
'timemodified' => $grade->timemodified,
],
'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'),
'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'),
]),
// Fetch the updated grade back out.
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
- return fetch::get_fetch_data($grade, $hasgrade);
+ return fetch::get_fetch_data($grade, $hasgrade, 0);
}
/**
$gradeduser = \core_user::get_user($gradeduserid);
- return self::get_fetch_data($gradeitem, $gradeduser);
+ $maxgrade = (int) $gradeitem->get_grade_item()->grademax;
+
+ return self::get_fetch_data($gradeitem, $gradeduser, $maxgrade);
}
/**
*
* @param gradeitem $gradeitem
* @param stdClass $gradeduser
+ * @param int $maxgrade
* @return array
*/
- public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser): array {
+ public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser, int $maxgrade): array {
global $USER;
$hasgrade = $gradeitem->user_has_grade($gradeduser);
'hasgrade' => $hasgrade,
'grade' => [
'options' => $values,
+ 'usergrade' => $grade->grade,
+ 'maxgrade' => $maxgrade,
'timecreated' => $grade->timecreated,
'timemodified' => $grade->timemodified,
],
]),
'The description of the grade option'
),
+ 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'),
+ 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'),
'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'),
]),
$gradeitem->send_student_notification($gradeduser, $USER);
}
- return fetch::get_fetch_data($gradeitem, $gradeduser);
+ return fetch::get_fetch_data($gradeitem, $gradeduser, 0);
}
/**
$fillings = $instance->get_guide_filling();
$context = $controller->get_context();
$definitionid = (int) $definition->id;
+ $maxgrade = max(array_keys($controller->get_grade_range()));
$criterion = [];
if ($definition->guide_criteria) {
'criterion' => $criterion,
'hascomments' => !empty($comments),
'comments' => $comments,
+ 'usergrade' => $grade->grade,
+ 'maxgrade' => $maxgrade,
'timecreated' => $grade->timecreated,
'timemodified' => $grade->timemodified,
],
]),
'Frequently used comments'
),
+ 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'),
+ 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'),
'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'),
]),
$fillings = $instance->get_rubric_filling();
$context = $controller->get_context();
$definitionid = (int) $definition->id;
-
+ $maxgrade = max(array_keys($controller->get_grade_range()));
$teacherdescription = self::get_formatted_text(
$context,
$definitionid,
'rubricmode' => 'evaluate editable',
'teacherdescription' => $teacherdescription,
'canedit' => false,
+ 'usergrade' => $grade->grade,
+ 'maxgrade' => $maxgrade,
'timecreated' => $grade->timecreated,
'timemodified' => $grade->timemodified,
],
])),
])
),
- 'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'),
+ 'usergrade' => new external_value(PARAM_RAW, 'Current user grade'),
+ 'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'),
'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'),
]),
'warnings' => new external_warnings(),
);
};
+/**
+ * Launch the Grader.
+ *
+ * @param {HTMLElement} rootNode the root HTML element describing what is to be graded
+ */
+const launchViewGrading = async rootNode => {
+ const data = rootNode.dataset;
+ const gradingPanelFunctions = await Grader.getGradingPanelFunctions(
+ 'mod_forum',
+ data.contextid,
+ data.gradingComponent,
+ data.gradingComponentSubtype,
+ data.gradableItemtype
+ );
+
+ await Grader.view(
+ gradingPanelFunctions.getter,
+ data.userid,
+ data.name
+ );
+};
+
/**
* Register listeners to launch the grading panel.
*/
throw Error('Unable to find a valid gradable item');
}
}
+ if (e.target.matches(Selectors.viewGrade)) {
+ e.preventDefault();
+ const rootNode = findGradableNode(e.target);
+
+ if (!rootNode) {
+ throw Error('Unable to find a gradable item');
+ }
+
+ if (rootNode.matches(Selectors.gradableItems.wholeForum)) {
+ // Note: The preventDefault must be before any async function calls because the function becomes async
+ // at that point and the default action is implemented.
+ e.preventDefault();
+ try {
+ await launchViewGrading(rootNode);
+ } catch (error) {
+ Notification.exception(error);
+ }
+ } else {
+ throw Error('Unable to find a valid gradable item');
+ }
+ }
});
};
},
expandConversation: '[data-action="view-context"]',
posts: '[data-region="posts"]',
+ viewGrade: '[data-grade-action="view"]',
};
import {addIconToContainerWithPromise} from 'core/loadingicon';
import {debounce} from 'core/utils';
import {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';
+import * as Modal from 'core/modal_factory';
+import * as ModalEvents from 'core/modal_events';
const templateNames = {
grader: {
displayUserPicker(graderContainer, userPicker.rootNode);
};
+/**
+ * Show the grade for a specific user.
+ *
+ * @param {Function} getGradeForUser A function get the grade details for a specific user
+ * @param {Number} userid The ID of a specific user
+ */
+export const view = async(getGradeForUser, userid, moduleName) => {
+
+ const [
+ userGrade,
+ modal,
+ ] = await Promise.all([
+ getGradeForUser(userid),
+ Modal.create({
+ title: moduleName,
+ large: true,
+ type: Modal.types.CANCEL
+ }),
+ ]);
+
+ const spinner = addIconToContainerWithPromise(modal.getRoot());
+
+ // Handle hidden event.
+ modal.getRoot().on(ModalEvents.hidden, function() {
+ // Destroy when hidden.
+ modal.destroy();
+ });
+
+ modal.show();
+ const output = document.createElement('div');
+ const {html, js} = await Templates.renderForPromise('mod_forum/local/grades/view_grade', userGrade);
+ Templates.replaceNodeContents(output, html, js);
+
+ // Note: We do not use await here because it messes with the Modal transitions.
+ const [gradeHTML, gradeJS] = await renderGradeTemplate(userGrade);
+ const gradeReplace = output.querySelector('[data-region="grade-template"]');
+ Templates.replaceNodeContents(gradeReplace, gradeHTML, gradeJS);
+ modal.setBody(output.outerHTML);
+ spinner.resolve();
+};
+
+const renderGradeTemplate = async(userGrade) => {
+ const {html, js} = await Templates.renderForPromise(userGrade.templatename, userGrade.grade);
+ return [html, js];
+};
export {getGradingPanelFunctions};
use coding_exception;
use context;
use core_grades\component_gradeitem;
-use core_grades\component_gradeitems;
-use gradingform_instance;
+use core_grades\local\gradeitem as gradeitem;
use mod_forum\local\container as forum_container;
use mod_forum\local\entities\forum as forum_entity;
use required_capability_exception;
]);
}
+ /**
+ * 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
+ */
+ public function get_grade_instance_id(): int {
+ return (int) $this->forum->get_id();
+ }
+
/**
* Create or update the grade.
*
'excludesubscription' => true
],
'totaldiscussioncount' => $alldiscussionscount,
+ 'userid' => $user->id,
'visiblediscussioncount' => count($discussions)
];
$string['grade_forum_header'] = 'Whole forum grading';
$string['grade_forum_title'] = 'Grade';
+$string['gradingstatus'] = 'Grade status:';
+$string['graded'] = 'Graded';
+$string['notgraded'] = 'Not graded';
$string['gradeforrating'] = 'Grade for rating: {$a->str_long_grade}';
$string['gradeforratinghidden'] = 'Grade for rating hidden';
$string['gradeforwholeforum'] = 'Grade for forum: {$a->str_long_grade}';
$string['grades:gradesavefailed'] = 'Unable to save grade for {$a->fullname}: {$a->error}';
$string['showmoreusers'] = 'Show more users';
$string['nousersmatch'] = 'No user(s) found for given criteria';
+$string['viewgrades'] = 'View grades';
// Deprecated since Moodle 3.8.
$string['cannotdeletediscussioninsinglediscussion'] = 'You cannot delete the first post in a single discussion';
{{> mod_forum/grades/grade_button }}
{{/forum.state.gradingenabled}}
{{/forum.capabilities.grade}}
+ {{^forum.capabilities.grade}}
+ {{#forum.state.gradingenabled}}
+ {{> mod_forum/grades/view_grade_button }}
+ {{/forum.state.gradingenabled}}
+ {{/forum.capabilities.grade}}
</div>
{{#forum.capabilities.create}}
<div class="collapse m-t-1 p-b-1" id="collapseAddForm">
--- /dev/null
+{{!
+ 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/>.
+}}
+{{!
+ @template mod_forum/grades/grade_button
+
+ Template which defines a forum post for sending in a single-post HTML email.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Example context (json):
+ {
+ }
+}}
+<button
+ class="btn btn-secondary"
+ data-grade-action="view"
+ data-contextid="{{contextid}}"
+ data-cmid="{{cmid}}"
+ data-name="{{name}}"
+ data-group="{{groupid}}"
+ data-userid="{{userid}}"
+ data-grading-component="{{gradingcomponent}}"
+ data-grading-component-subtype="{{gradingcomponentsubtype}}"
+ data-gradable-itemtype="forum"
+ >
+ {{#str}}viewgrades, forum{{/str}}
+</button>
+{{#js}}
+ require(['mod_forum/grades/grader'], function(Grader) {
+ Grader.registerLaunchListeners();
+ });
+{{/js}}
--- /dev/null
+{{!
+ 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/>.
+}}
+{{!
+ @template mod_forum/local/grades/local/view_grade
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * data-region="grade-template"
+
+ Context variables required for this template:
+ * none
+
+ Example context (json):
+ {
+ "templatename": "core_grades/grades/grader/gradingpanel/point",
+ "grade": {
+ "usergrade": 55,
+ "maxgrade": 100,
+ "timemodified": 1573543598
+ },
+ "hasgrade": true
+ }
+}}
+<div class="container-fluid grade-display" data-region="view-grade">
+ {{#grade}}
+ <div class="row-fluid px-3">
+ <h5 class="font-weight-bold description">{{#str}}grade{{/str}}:</h5>
+ <p class="ml-auto">{{usergrade}} / {{maxgrade}}</p>
+ </div>
+ <div class="row-fluid px-3">
+ <h5 class="font-weight-bold description">{{#str}}date{{/str}}:</h5>
+ <p class="ml-auto">{{#userdate}}{{timemodified}}, {{#str}} strftimedate, langconfig {{/str}}{{/userdate}}</p>
+ </div>
+ {{/grade}}
+ <div class="row-fluid px-3">
+ <h5 class="font-weight-bold description">
+ {{#str}}gradingstatus, forum{{/str}}
+ </h5>
+ <p class="ml-auto">
+ {{#hasgrade}}
+ {{#str}}graded, forum{{/str}}
+ {{/hasgrade}}
+ {{^hasgrade}}
+ {{#str}}notgraded, forum{{/str}}
+ {{/hasgrade}}
+ </p>
+ </div>
+ <div class="row-fluid p-3">
+ <fieldset class="w-100" disabled="disabled">
+ <div class="w-100" data-region="grade-template"></div>
+ </fieldset>
+ </div>
+</div>
$rendererfactory = mod_forum\local\container::get_renderer_factory();
switch ($forum->get_type()) {
case 'single':
+ $forumgradeitem = forum_gradeitem::load_from_forum_entity($forum);
if ($capabilitymanager->can_grade($USER)) {
- $forumgradeitem = forum_gradeitem::load_from_forum_entity($forum);
+
if ($forumgradeitem->is_grading_enabled()) {
$groupid = groups_get_activity_group($cm, true) ?: null;
$gradeobj = (object) [
];
echo $OUTPUT->render_from_template('mod_forum/grades/grade_button', $gradeobj);
}
+ } else {
+ if ($forumgradeitem->is_grading_enabled()) {
+ $groupid = groups_get_activity_group($cm, true) ?: null;
+ $gradeobj = (object) [
+ 'contextid' => $forum->get_context()->id,
+ 'cmid' => $cmid,
+ 'name' => $forum->get_name(),
+ 'courseid' => $course->id,
+ 'coursename' => $course->shortname,
+ 'groupid' => $groupid,
+ 'userid' => $USER->id,
+ 'gradingcomponent' => $forumgradeitem->get_grading_component_name(),
+ 'gradingcomponentsubtype' => $forumgradeitem->get_grading_component_subtype(),
+ ];
+ $OUTPUT->render_from_template('mod_forum/grades/view_grade_button', $gradeobj);
+ }
}
$discussion = $discussionvault->get_last_discussion_in_forum($forum);
$discussioncount = $discussionvault->get_count_discussions_in_forum($forum);
}
}
}
-
+.grade-display {
+ .description {
+ font-size: 1rem;
+ }
+}
.criterion {
.description {
font-size: 1rem;
margin-left: 5px;
margin-right: 12px; }
+.grade-display .description {
+ font-size: 1rem; }
+
.criterion .description {
font-size: 1rem; }
margin-left: 5px;
margin-right: 12px; }
+.grade-display .description {
+ font-size: 1rem; }
+
.criterion .description {
font-size: 1rem; }