MDL-68394 h5pactivity: Implement methods to display recent

activity info.
This commit is contained in:
Ilya Tregubov 2021-01-20 14:18:09 +02:00
parent efb3d4e7a7
commit 045c3ec3ee
5 changed files with 662 additions and 1 deletions

View File

@ -77,6 +77,7 @@ $string['grade_highest_attempt'] = 'Highest grade';
$string['grade_average_attempt'] = 'Average grade';
$string['grade_last_attempt'] = 'Last attempt';
$string['grade_first_attempt'] = 'First attempt';
$string['grade_h5p'] = 'Grade: {$a}';
$string['h5pactivity:addinstance'] = 'Add a new H5P';
$string['h5pactivity:reviewattempts'] = 'Review H5P attempts';
$string['h5pactivity:submit'] = 'Submit H5P attempts';
@ -94,6 +95,7 @@ Any question attempts are marked automatically, and the grade is recorded in the
$string['modulename_link'] = 'mod/h5pactivity/view';
$string['modulenameplural'] = 'H5P';
$string['myattempts'] = 'My attempts';
$string['newsubmissions'] = 'H5P submitted';
$string['no_compatible_track'] = 'This interaction ({$a}) does not provide tracking information or the tracking
provided is not compatible with the current activity version.';
$string['noparticipants'] = 'No participants to display';

View File

@ -261,7 +261,7 @@ function h5pactivity_reset_course_form_defaults(stdClass $course): array {
* @return array of reseting status
*/
function h5pactivity_reset_userdata(stdClass $data): array {
global $CFG, $DB;
global $DB;
$componentstr = get_string('modulenameplural', 'mod_h5pactivity');
$status = [];
if (!empty($data->reset_h5pactivity)) {
@ -541,3 +541,283 @@ function h5pactivity_dndupload_handle($uploadinfo): int {
return h5pactivity_add_instance($h5p, null);
}
/**
* Print recent activity from all h5pactivities in a given course
*
* This is used by the recent activity block
* @param mixed $course the course to print activity for
* @param bool $viewfullnames boolean to determine whether to show full names or not
* @param int $timestart the time the rendering started
* @return bool true if activity was printed, false otherwise.
*/
function h5pactivity_print_recent_activity($course, bool $viewfullnames, int $timestart): bool {
global $CFG, $DB, $OUTPUT, $USER;
$dbparams = [$timestart, $course->id, 'h5pactivity'];
$userfieldsapi = \core_user\fields::for_userpic();
$namefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;;
$sql = "SELECT h5pa.id, h5pa.timemodified, cm.id as cmid, $namefields
FROM {h5pactivity_attempts} h5pa
JOIN {h5pactivity} h5p ON h5p.id = h5pa.h5pactivityid
JOIN {course_modules} cm ON cm.instance = h5p.id
JOIN {modules} md ON md.id = cm.module
JOIN {user} u ON u.id = h5pa.userid
WHERE h5pa.timemodified > ?
AND h5p.course = ?
AND md.name = ?
ORDER BY h5pa.timemodified ASC";
if (!$submissions = $DB->get_records_sql($sql, $dbparams)) {
return false;
}
$modinfo = get_fast_modinfo($course);
$recentactivity = h5pactivity_fetch_recent_activity($submissions, $course->id);
if (empty($recentactivity)) {
return false;
}
$cms = $modinfo->get_cms();
echo $OUTPUT->heading(get_string('newsubmissions', 'h5pactivity') . ':', 6);
foreach ($recentactivity as $submission) {
$cm = $cms[$submission->cmid];
$link = $CFG->wwwroot.'/mod/h5pactivity/view.php?id='.$cm->id;
print_recent_activity_note($submission->timemodified,
$submission,
$cm->name,
$link,
false,
$viewfullnames);
}
return true;
}
/**
* Returns all h5pactivities since a given time.
*
* @param array $activities The activity information is returned in this array
* @param int $index The current index in the activities array
* @param int $timestart The earliest activity to show
* @param int $courseid Limit the search to this course
* @param int $cmid The course module id
* @param int $userid Optional user id
* @param int $groupid Optional group id
* @return void
*/
function h5pactivity_get_recent_mod_activity(array &$activities, int &$index, int $timestart, int $courseid,
int $cmid, int $userid=0, int $groupid=0) {
global $CFG, $DB, $USER;
$course = get_course($courseid);
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->get_cm($cmid);
$params = [];
if ($userid) {
$userselect = 'AND u.id = :userid';
$params['userid'] = $userid;
} else {
$userselect = '';
}
if ($groupid) {
$groupselect = 'AND gm.groupid = :groupid';
$groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id';
$params['groupid'] = $groupid;
} else {
$groupselect = '';
$groupjoin = '';
}
$params['cminstance'] = $cm->instance;
$params['timestart'] = $timestart;
$userfieldsapi = \core_user\fields::for_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
$sql = "SELECT h5pa.id, h5pa.timemodified, cm.id as cmid, $userfields
FROM {h5pactivity_attempts} h5pa
JOIN {h5pactivity} h5p ON h5p.id = h5pa.h5pactivityid
JOIN {course_modules} cm ON cm.instance = h5p.id
JOIN {modules} md ON md.id = cm.module
JOIN {user} u ON u.id = h5pa.userid $groupjoin
WHERE h5pa.timemodified > :timestart
AND h5p.id = :cminstance $userselect $groupselect
ORDER BY h5pa.timemodified ASC";
if (!$submissions = $DB->get_records_sql($sql, $params)) {
return;
}
$cmcontext = context_module::instance($cm->id);
$grader = has_capability('mod/h5pactivity:reviewattempts', $cmcontext);
$viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext);
$recentactivity = h5pactivity_fetch_recent_activity($submissions, $courseid);
if (empty($recentactivity)) {
return;
}
if ($grader) {
require_once($CFG->libdir.'/gradelib.php');
$userids = [];
foreach ($recentactivity as $id => $submission) {
$userids[] = $submission->userid;
}
$grades = grade_get_grades($courseid, 'mod', 'h5pactivity', $cm->instance, $userids);
}
$aname = format_string($cm->name, true);
foreach ($recentactivity as $submission) {
$activity = new stdClass();
$activity->type = 'h5pactivity';
$activity->cmid = $cm->id;
$activity->name = $aname;
$activity->sectionnum = $cm->sectionnum;
$activity->timestamp = $submission->timemodified;
$activity->user = new stdClass();
if ($grader) {
$activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
}
$userfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
foreach ($userfields as $userfield) {
if ($userfield == 'id') {
// Aliased in SQL above.
$activity->user->{$userfield} = $submission->userid;
} else {
$activity->user->{$userfield} = $submission->{$userfield};
}
}
$activity->user->fullname = fullname($submission, $viewfullnames);
$activities[$index++] = $activity;
}
return;
}
/**
* Print recent activity from all h5pactivities in a given course
*
* This is used by course/recent.php
* @param stdClass $activity
* @param int $courseid
* @param bool $detail
* @param array $modnames
*/
function h5pactivity_print_recent_mod_activity(stdClass $activity, int $courseid, bool $detail, array $modnames) {
global $OUTPUT;
$modinfo = [];
if ($detail) {
$modinfo['modname'] = $activity->name;
$modinfo['modurl'] = new moodle_url('/mod/h5pactivity/view.php', ['id' => $activity->cmid]);
$modinfo['modicon'] = $OUTPUT->image_icon('icon', $modnames[$activity->type], 'h5pactivity');
}
$userpicture = $OUTPUT->user_picture($activity->user);
$template = ['userpicture' => $userpicture,
'submissiontimestamp' => $activity->timestamp,
'modinfo' => $modinfo,
'userurl' => new moodle_url('/user/view.php', array('id' => $activity->user->id, 'course' => $courseid)),
'fullname' => $activity->user->fullname];
if (isset($activity->grade)) {
$template['grade'] = get_string('grade_h5p', 'h5pactivity', $activity->grade);
}
echo $OUTPUT->render_from_template('mod_h5pactivity/reviewattempts', $template);
}
/**
* Fetches recent activity for course module.
*
* @param array $submissions The activity submissions
* @param int $courseid Limit the search to this course
* @return array $recentactivity recent activity in a course.
*/
function h5pactivity_fetch_recent_activity(array $submissions, int $courseid) : array {
global $USER;
$course = get_course($courseid);
$modinfo = get_fast_modinfo($course);
$recentactivity = [];
$grader = [];
$cms = $modinfo->get_cms();
foreach ($submissions as $submission) {
if (!array_key_exists($submission->cmid, $cms)) {
continue;
}
$cm = $cms[$submission->cmid];
if (!$cm->uservisible) {
continue;
}
if ($USER->id == $submission->userid) {
$recentactivity[$submission->userid] = $submission;
continue;
}
$cmcontext = context_module::instance($cm->id);
// The act of submitting of attempt may be considered private -
// only graders will see it if specified.
if (!array_key_exists($cm->id, $grader)) {
$grader[$cm->id] = has_capability('mod/h5pactivity:reviewattempts', $cmcontext);
}
if (!$grader[$cm->id]) {
continue;
}
$groups = [];
$usersgroups = [];
$groupmode = groups_get_activity_groupmode($cm, $course);
$accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
if ($groupmode == SEPARATEGROUPS && !$accessallgroups) {
if (isguestuser()) {
// Shortcut - guest user does not belong into any group.
continue;
}
if (!isset($groups[$cm->groupingid])) {
$groups[$cm->groupingid] = $modinfo->get_groups($cm->groupingid);
if (!$groups[$cm->groupingid]) {
continue;
}
}
if (!isset($usersgroups[$cm->groupingid][$submission->userid])) {
$usersgroups[$cm->groupingid][$submission->userid] =
groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
}
if (is_array($usersgroups[$cm->groupingid][$submission->userid])) {
$usersgroupstmp = array_keys($usersgroups[$cm->groupingid][$submission->userid]);
$intersect = array_intersect($usersgroupstmp, $groups[$cm->groupingid]);
if (empty($intersect)) {
continue;
}
}
}
$recentactivity[$submission->userid] = $submission;
}
return $recentactivity;
}

View File

@ -0,0 +1,58 @@
{{!
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_h5pactivity/reviewattempts
This template will render the H5P activity inside a recent activity report.
Variables requirer for this template:
* userpicture - User fullname and picture
* grade - User grade
* userdate - Submission date
Example context (json):
{
"userpicture": "<a href=\"#\"> </a>",
"grade": "30.77 / 100.00",
"userdate": "Wednesday, 27 January 2021, 9:54 AM",
"userurl": "http://localhost/m/stable_master/user/view.php?id=3&course=6",
"fullname": "Lukáš Svoboda"
}
}}
<table class="h5pactivity-recent">
<tbody>
<tr>
<td class="userpicture">{{{userpicture}}}</td>
<td>
<div class="title">
{{#modinfo}}
{{modicon}}<a href="{{modurl}}">{{modname}}</a>
{{/modinfo}}
</div>
{{#grade}}
<div class="grade">
{{grade}}
</div>
{{/grade}}
<div class="user">
<a href="{{userurl}}">{{fullname}}</a> - {{#userdate}} {{submissiontimestamp}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}
</div>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,79 @@
@mod @mod_h5pactivity @core_h5p @_file_upload @_switch_iframe
Feature: Users can see the H5P recent activity from the recent activity block
In order to quickly see the updates from H5P activity in my course
As a user
I need to be able to see the recent activity in the recent activity block
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/h5p:updatelibraries | Allow | editingteacher | System | |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "H5P" to section "1"
And I set the following fields to these values:
| Name | Awesome H5P package |
| Description | Description |
And I upload "h5p/tests/fixtures/multiple-choice-2-6.h5p" file to "Package file" filemanager
And I click on "Save and return to course" "button"
And I add the "Recent activity" block
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Awesome H5P package"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
And I click on "Wrong one" "text" in the ".h5p-question-content" "css_element"
And I click on "Check" "button" in the ".h5p-question-buttons" "css_element"
And I switch to the main frame
And I log out
And I log in as "student2"
And I am on "Course 1" course homepage
And I follow "Awesome H5P package"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
And I click on "Correct one" "text" in the ".h5p-question-content" "css_element"
And I click on "Check" "button" in the ".h5p-question-buttons" "css_element"
And I switch to the main frame
And I log out
@javascript
Scenario: Student see only his own activity
Given I log in as "student1"
And I am on "Course 1" course homepage
And I should see "H5P submitted:" in the "Recent activity" "block"
And I should see "Student 1" in the "Recent activity" "block"
And I should not see "Grade:" in the "Recent activity" "block"
And I should not see "Student 2" in the "Recent activity" "block"
When I click on "Full report of recent activity" "link"
Then I should see "H5P Awesome H5P package"
And I should see "Student 1 - "
And I should not see "Grade:"
And I should not see "Student 2 - "
@javascript
Scenario: Teacher see each student activity
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I should see "H5P submitted:" in the "Recent activity" "block"
And I should see "Student 1" in the "Recent activity" "block"
And I should not see "Grade:" in the "Recent activity" "block"
And I should see "Student 2" in the "Recent activity" "block"
When I click on "Full report of recent activity" "link"
Then I should see "H5P Awesome H5P package"
And I should see "Student 1 - "
And I should see "Grade:"
And I should see "Student 2 - "

View File

@ -0,0 +1,242 @@
<?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/>.
/**
* Unit tests for (some of) mod/h5pactivity/lib.php.
*
* @package mod_h5pactivity
* @copyright 2021 Ilya Tregubov <ilya@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_h5pactivity\local\manager;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/h5pactivity/lib.php');
/**
* Unit tests for (some of) mod/h5pactivity/lib.php.
*
* @copyright 2021 Ilya Tregubov <ilya@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends advanced_testcase {
/**
* Test that assign_print_recent_activity shows ungraded submitted assignments.
*/
public function test_print_recent_activity() {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => 1, 'grademethod' => manager::GRADEHIGHESTATTEMPT]);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
$manager = manager::create_from_instance($activity);
$cm = $manager->get_coursemodule();
$user = $student;
$params = ['cmid' => $cm->id, 'userid' => $user->id];
$generator->create_content($activity, $params);
$this->setUser($student);
$this->expectOutputRegex('/submitted:/');
h5pactivity_print_recent_activity($course, true, time() - 3600);
}
/**
* Test that h5pactivity_print_recent_activity does not display any warnings when a custom fullname has been configured.
*/
public function test_print_recent_activity_fullname() {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => 1, 'grademethod' => manager::GRADEHIGHESTATTEMPT]);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
$manager = manager::create_from_instance($activity);
$cm = $manager->get_coursemodule();
$user = $student;
$params = ['cmid' => $cm->id, 'userid' => $user->id];
$generator->create_content($activity, $params);
$this->setUser($teacher);
$this->expectOutputRegex('/submitted:/');
set_config('fullnamedisplay', 'firstname, lastnamephonetic');
h5pactivity_print_recent_activity($course, false, time() - 3600);
}
/**
* Test that h5pactivity_get_recent_mod_activity fetches the h5pactivity correctly.
*/
public function test_h5pactivity_get_recent_mod_activity() {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => 1, 'grademethod' => manager::GRADEHIGHESTATTEMPT]);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
$manager = manager::create_from_instance($activity);
$cm = $manager->get_coursemodule();
$user = $student;
$params = ['cmid' => $cm->id, 'userid' => $user->id];
$generator->create_content($activity, $params);
$index = 1;
$activities = [
$index => (object) [
'type' => 'h5pactivity',
'cmid' => $cm->id,
],
];
$this->setUser($teacher);
h5pactivity_get_recent_mod_activity($activities, $index, time() - HOURSECS, $course->id, $cm->id);
$activity = $activities[1];
$this->assertEquals("h5pactivity", $activity->type);
$this->assertEquals($student->id, $activity->user->id);
}
/**
* Test that h5pactivity_get_recent_mod_activity fetches activity correctly.
*/
public function test_h5pactivity_fetch_recent_activity() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
// Create users and groups.
$students = array();
$groups = array();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
for ($i = 1; $i < 4; $i++) {
$students[$i] = $this->getDataGenerator()->create_and_enrol($course, 'student');
$groups[$i] = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
}
// Update the course set the groupmode SEPARATEGROUPS and forced.
update_course((object)array('id' => $course->id, 'groupmode' => SEPARATEGROUPS, 'groupmodeforce' => true));
// Student 1 is in groups 1 and 3.
groups_add_member($groups[1], $students[1]);
groups_add_member($groups[3], $students[1]);
// Student 2 is in groups 1 and 2.
groups_add_member($groups[1], $students[2]);
groups_add_member($groups[2], $students[2]);
// Student 3 is only in group 3.
groups_add_member($groups[3], $students[3]);
// Grader is only in group 3.
groups_add_member($groups[3], $teacher);
$timestart = time() - 1;
// Create h5pactivity.
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course->id, 'enabletracking' => 1, 'grademethod' => manager::GRADEHIGHESTATTEMPT,
'groupmode' => SEPARATEGROUPS]);
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$cmcontext = \context_module::instance($activity->cmid);
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $cmcontext, true);
$manager = manager::create_from_instance($activity);
$cm = $manager->get_coursemodule();
// Create attempts.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
foreach ($students as $student) {
$params = ['cmid' => $cm->id, 'userid' => $student->id];
$generator->create_content($activity, $params);
}
// Get all attempts.
$dbparams = [$timestart, $course->id, 'h5pactivity'];
$userfieldsapi = \core_user\fields::for_userpic();
$namefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;;
$sql = "SELECT h5pa.id, h5pa.timemodified, cm.id as cmid, $namefields
FROM {h5pactivity_attempts} h5pa
JOIN {h5pactivity} h5p ON h5p.id = h5pa.h5pactivityid
JOIN {course_modules} cm ON cm.instance = h5p.id
JOIN {modules} md ON md.id = cm.module
JOIN {user} u ON u.id = h5pa.userid
WHERE h5pa.timemodified > ?
AND h5p.course = ?
AND md.name = ?
ORDER BY h5pa.timemodified ASC";
$submissions = $DB->get_records_sql($sql, $dbparams);
$this->assertCount(count($students), $submissions);
// Fetch activity for student (only his own).
$this->setUser($students[1]);
$recentactivity = h5pactivity_fetch_recent_activity($submissions, $course->id);
$this->assertCount(1, $recentactivity);
$this->assertEquals($students[1]->id, $recentactivity[$students[1]->id]->userid);
// Fetch users group info for grader.
$this->setUser($teacher);
$recentactivity = h5pactivity_fetch_recent_activity($submissions, $course->id);
$this->assertCount(2, $recentactivity);
// Grader, Student 1 and 3 are in Group 3.
$this->assertEquals($students[1]->id, $recentactivity[$students[1]->id]->userid);
$this->assertEquals($students[3]->id, $recentactivity[$students[3]->id]->userid);
// Grader is in Group 2.
groups_remove_member($groups[3], $teacher);
groups_add_member($groups[2], $teacher);
get_fast_modinfo($course->id, 0, true);
$recentactivity = h5pactivity_fetch_recent_activity($submissions, $course->id);
$this->assertCount(1, $recentactivity);
// Grader, Student 2 are in Group 2.
$this->assertEquals($students[2]->id, $recentactivity[$students[2]->id]->userid);
// Grader is in Group 1.
groups_remove_member($groups[2], $teacher);
groups_add_member($groups[1], $teacher);
get_fast_modinfo($course->id, 0, true);
$recentactivity = h5pactivity_fetch_recent_activity($submissions, $course->id);
$this->assertCount(2, $recentactivity);
// Grader, Student 1 and 2 are in Group 1.
$this->assertEquals($students[1]->id, $recentactivity[$students[1]->id]->userid);
$this->assertEquals($students[2]->id, $recentactivity[$students[2]->id]->userid);
}
}