Skip to content

Commit

Permalink
CTP-3990 Marks items under gradebook category as summative
Browse files Browse the repository at this point in the history
  • Loading branch information
aydevworks committed Dec 10, 2024
1 parent 1e1ee5b commit 42846d0
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 17 deletions.
122 changes: 106 additions & 16 deletions classes/assesstype.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,19 @@ public static function update_assess_type(int|\stdClass $mapping, string $action
return;
}

// Mapped assessment is a course module, set grdade item ID to 0.
if ($mapping->sourcetype === assessmentfactory::SOURCETYPE_MOD) {
$cmid = $mapping->sourceid;
$gradeitemid = 0;
} else {
// Mapped assessment is a gradebook item or category, set course module ID to 0.
$cmid = 0;
$assessment = assessmentfactory::get_assessment($mapping->sourcetype, $mapping->sourceid);
$gradeitems = $assessment->get_grade_items();
$gradeitemid = $gradeitems[0]->id;
}
// Set course module ID if the mapped assessment is a course module, otherwise set to 0.
$cmid = $mapping->sourcetype === assessmentfactory::SOURCETYPE_MOD ? $mapping->sourceid : 0;

// Everything that is not a course module is a grade item or category.
$gradeitemid = $cmid ? 0 : assessmentfactory::get_assessment($mapping->sourcetype, $mapping->sourceid)
->get_grade_items()[0]->id;

$lockstatus = $action === self::ACTION_LOCK ? 1 : 0;
assess_type::update_type($mapping->courseid, assess_type::ASSESS_TYPE_SUMMATIVE, $cmid, $gradeitemid, $lockstatus);

// Set assessment type and lock status.
if ($action === self::ACTION_LOCK) {
assess_type::update_type($mapping->courseid, assess_type::ASSESS_TYPE_SUMMATIVE, $cmid, $gradeitemid, 1);
} else if ($action === self::ACTION_UNLOCK) {
assess_type::update_type($mapping->courseid, assess_type::ASSESS_TYPE_SUMMATIVE, $cmid, $gradeitemid, 0);
// Update assess type for items (grade items or activities) under the grade category.
if ($mapping->sourcetype === assessmentfactory::SOURCETYPE_GRADE_CATEGORY) {
self::update_assess_type_items_under_gradecategory($action, $mapping->sourceid);
}
} catch (\Exception $e) {
logger::log('Failed to update assessment type and lock status.', null, null, $e->getMessage());
Expand All @@ -96,4 +92,98 @@ public static function is_assess_type_installed(): bool {
'local_assess_type'
);
}

/**
* Update assessment type and lock status for grade item when it is updated outside marks transfer.
*
* @param \core\event\grade_item_updated $event
* @return void
* @throws \coding_exception
* @throws \dml_exception
*/
public static function grade_item_updated(\core\event\grade_item_updated $event): void {
global $DB;

// Skip if assessment type plugin is not installed.
if (!self::is_assess_type_installed()) {
return;
}

$gradeitem = $event->get_record_snapshot('grade_items', $event->objectid);

// Only proceed for non-category grade items.
if ($gradeitem->itemtype === 'category') {
return;
}

// Determine source type and source ID based on item type.
switch ($gradeitem->itemtype) {
case 'manual':
$sourcetype = assessmentfactory::SOURCETYPE_GRADE_ITEM;
$sourceid = $gradeitem->id;
break;
case 'mod':
$sourcetype = assessmentfactory::SOURCETYPE_MOD;
$sourceid = get_coursemodule_from_instance(
$gradeitem->itemmodule,
$gradeitem->iteminstance,
$gradeitem->courseid
)->id;
break;
default:
return;
}

// Skip if grade item or activity is mapped. It will be handled by the mapping / unmapping actions.
if ($DB->record_exists(manager::TABLE_ASSESSMENT_MAPPING, ['sourcetype' => $sourcetype, 'sourceid' => $sourceid])) {
return;
}

// Depending on the grade item's category. If the category is mapped, mark it summative and lock it.
// Otherwise, unlock it.
$action = $DB->record_exists(
manager::TABLE_ASSESSMENT_MAPPING,
['sourcetype' => assessmentfactory::SOURCETYPE_GRADE_CATEGORY, 'sourceid' => $gradeitem->categoryid]
) ? self::ACTION_LOCK : self::ACTION_UNLOCK;

self::update_assess_type_items_under_gradecategory($action, $gradeitem->categoryid, $gradeitem);
}

/**
* Update assess type for grade items and activities under a grade category.
*
* @param string $action
* @param int $categoryid
* @param \stdClass|null $gradeitem
* @return void
* @throws \coding_exception
* @throws \dml_exception
*/
private static function update_assess_type_items_under_gradecategory(
string $action,
int $categoryid,
?\stdClass $gradeitem = null
): void {
$lockstatus = $action === self::ACTION_LOCK ? 1 : 0;
$gradeitems = $gradeitem ? [$gradeitem] : \grade_item::fetch_all(['categoryid' => $categoryid]);

foreach ($gradeitems as $item) {
if (!in_array($item->itemtype, ['mod', 'manual'])) {
continue;
}

$cmid = $item->itemtype === 'mod'
? get_coursemodule_from_instance($item->itemmodule, $item->iteminstance, $item->courseid)->id
: 0;
$gradeitemid = $item->itemtype === 'manual' ? $item->id : 0;

assess_type::update_type(
$item->courseid,
assess_type::ASSESS_TYPE_SUMMATIVE,
$cmid,
$gradeitemid,
$lockstatus
);
}
}
}
5 changes: 4 additions & 1 deletion classes/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -1457,7 +1457,7 @@ public function remove_mapping(int $courseid, int $mappingid): void {
}

// Check the mapping exists.
if (!$DB->record_exists(self::TABLE_ASSESSMENT_MAPPING, ['id' => $mappingid])) {
if (!$mapping = $DB->get_record(self::TABLE_ASSESSMENT_MAPPING, ['id' => $mappingid])) {
throw new \moodle_exception('error:assessmentmapping', 'local_sitsgradepush', '', $mappingid);
}

Expand All @@ -1468,6 +1468,9 @@ public function remove_mapping(int $courseid, int $mappingid): void {

// Everything is fine, remove the mapping.
$DB->delete_records(self::TABLE_ASSESSMENT_MAPPING, ['id' => $mappingid]);

// Unlock the Moodle assessment in the local_assess_type plugin.
assesstype::update_assess_type($mapping, assesstype::ACTION_UNLOCK);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions classes/observer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

use local_sitsgradepush\assessment\assessmentfactory;
use local_sitsgradepush\assesstype;
use local_sitsgradepush\cachemanager;
use local_sitsgradepush\manager;

Expand Down Expand Up @@ -92,4 +94,14 @@ public static function assessment_mapped(\local_sitsgradepush\event\assessment_m
cachemanager::purge_cache(cachemanager::CACHE_AREA_STUDENTSPR, $key);
}
}

/**
* Handle the grade item updated event.
*
* @param \core\event\grade_item_updated $event
* @return void
*/
public static function grade_item_updated(\core\event\grade_item_updated $event): void {
assesstype::grade_item_updated($event);
}
}
4 changes: 4 additions & 0 deletions db/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@
'callback' => 'local_sitsgradepush_observer::assessment_mapped',
'priority' => 200,
],
[
'eventname' => '\core\event\grade_item_updated',
'callback' => 'local_sitsgradepush_observer::grade_item_updated',
],
];
187 changes: 187 additions & 0 deletions tests/assesstype/assesstype_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?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 local_sitsgradepush;

use local_sitsgradepush\assessment\assessmentfactory;

defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/local/sitsgradepush/tests/fixtures/tests_data_provider.php');

/**
* Tests for the assesstype class.
*
* @package local_sitsgradepush
* @copyright 2024 onwards University College London {@link https://www.ucl.ac.uk/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Alex Yeung <k.yeung@ucl.ac.uk>
*/
final class assesstype_test extends \advanced_testcase {
/** @var \stdClass Test course */
private \stdClass $course;

/** @var \stdClass Test grade item */
private \stdClass $gradeitem;

/** @var \stdClass Test grade category */
private \stdClass $gradecategory;

/** @var \stdClass Test assignment */
private \stdClass $assign;

/** @var \stdClass Test quiz */
private \stdClass $quiz;

/**
* Set up the test.
*
* @return void
*/
protected function setUp(): void {
parent::setUp();
// Skip the test if the plugin is not installed.
if (!assesstype::is_assess_type_installed()) {
$this->markTestSkipped('local_assess_type plugin is not installed.');
}
$this->resetAfterTest();
}

/**
* Test items under grade category marked summative and locked.
*
* @covers \local_sitsgradepush\assesstype::update_assess_type
* @covers \local_sitsgradepush\assesstype::is_assess_type_installed
* @covers \local_sitsgradepush\assesstype::update_assess_type_items_under_gradecategory
* @return void
* @throws \dml_exception
*/
public function test_items_under_grade_category_marked_summative(): void {
global $DB;

$this->setup_environment();
$gradeitems = \grade_item::fetch_all(['categoryid' => $this->gradecategory->id]);
$this->assertCount(3, $gradeitems);

// Check category's grade item is marked as summative and locked.
$category = \grade_category::fetch(['id' => $this->gradecategory->id]);
$this->assertTrue(
$DB->record_exists('local_assess_type', ['gradeitemid' => $category->load_grade_item()->id, 'type' => 1, 'locked' => 1])
);

// Check manual grade item is marked as summative and locked.
$this->assertTrue(
$DB->record_exists('local_assess_type', ['gradeitemid' => $this->gradeitem->id, 'type' => 1, 'locked' => 1])
);
// Check assignment is marked as summative and locked.
$this->assertTrue(
$DB->record_exists('local_assess_type', ['cmid' => $this->assign->cmid, 'type' => 1, 'locked' => 1])
);
// Check assignment is marked as summative and locked.
$this->assertTrue(
$DB->record_exists('local_assess_type', ['cmid' => $this->quiz->cmid, 'type' => 1, 'locked' => 1])
);
}

/**
* Set up the testing environment.
*
* @return void
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
protected function setup_environment() {
global $CFG, $DB;
require_once($CFG->libdir . '/gradelib.php');

// Set up the testing environment.
tests_data_provider::import_sitsgradepush_grade_components();

// Set block_lifecycle 'late_summer_assessment_end_date'.
set_config('late_summer_assessment_end_' . date('Y'), date('Y-m-d', strtotime('+2 month')), 'block_lifecycle');

// Create a custom category and custom field.
$this->getDataGenerator()->create_custom_field_category(['name' => 'CLC']);
$this->getDataGenerator()->create_custom_field(['category' => 'CLC', 'shortname' => 'course_year']);

// Create test courses.
$this->course = $this->getDataGenerator()->create_course(
['shortname' => 'C1', 'customfields' => [
['shortname' => 'course_year', 'value' => date('Y')],
]]);
$this->gradecategory =
$this->getDataGenerator()->create_grade_category(['courseid' => $this->course->id]); // Create grade category.
$this->gradeitem = $this->create_grade_item(); // Create grade item.
$this->assign = $this->getDataGenerator()->create_module('assign', ['course' => $this->course->id]); // Create assignment.
$this->quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $this->course->id]); // Create quiz.

// Add grade item and activities to grade category.
$gradeitem = \grade_item::fetch(['id' => $this->gradeitem->id]);
$gradeitem->categoryid = $this->gradecategory->id;
$gradeitem->update();

// Add assignment to grade category.
$gradeitem = \grade_item::fetch(['itemtype' => 'mod', 'iteminstance' => $this->assign->id, 'itemmodule' => 'assign']);
$gradeitem->categoryid = $this->gradecategory->id;
$gradeitem->update();

// Add quiz to grade category.
$gradeitem = \grade_item::fetch(['itemtype' => 'mod', 'iteminstance' => $this->quiz->id, 'itemmodule' => 'quiz']);
$gradeitem->categoryid = $this->gradecategory->id;
$gradeitem->update();

// Add assessment mapping.
$mab = $DB->get_record(manager::TABLE_COMPONENT_GRADE, ['mapcode' => 'LAWS0024A6UF', 'mabseq' => '001']);
$mapping = new \stdClass();
$mapping->courseid = $this->course->id;
$mapping->sourcetype = assessmentfactory::SOURCETYPE_GRADE_CATEGORY;
$mapping->sourceid = $this->gradecategory->id;
$mapping->componentgradeid = $mab->id;
$mapping->reassessment = 0;

manager::get_manager()->save_assessment_mapping($mapping);
}

/**
* Create a grade item.
*
* @return \stdClass
*/
private function create_grade_item(): \stdClass {
// Create grade item.
return $this->getDataGenerator()->create_grade_item([
'courseid' => $this->course->id,
'itemname' => 'Grade item',
'gradetype' => GRADE_TYPE_VALUE,
'grademax' => 100,
'grademin' => 0,
'scaleid' => 0,
'multfactor' => 1.0,
'plusfactor' => 0.0,
'aggregationcoef' => 0.0,
'aggregationcoef2' => 0.0,
'sortorder' => 1,
'hidden' => 0,
'locked' => 0,
'locktime' => 0,
'needsupdate' => 0,
'weightoverride' => 0,
'timecreated' => time(),
'timemodified' => time(),
]);
}
}

0 comments on commit 42846d0

Please sign in to comment.