Skip to content

Commit

Permalink
MDL-68163 gradebook: add grade item duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
Syxton committed Jun 2, 2020
1 parent 111b293 commit 887679e
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 0 deletions.
11 changes: 11 additions & 0 deletions grade/edit/tree/index.php
Expand Up @@ -86,6 +86,17 @@
$grade_edit_tree = new grade_edit_tree($gtree, $movingeid, $gpr);

switch ($action) {
case 'duplicate':
if ($eid and confirm_sesskey()) {
if (!$el = $gtree->locate_element($eid)) {
print_error('invalidelementid', '', $returnurl);
}

$object->duplicate();
redirect($returnurl);
}
break;

case 'delete':
if ($eid && confirm_sesskey()) {
if (!$grade_edit_tree->element_deletable($element)) {
Expand Down
30 changes: 30 additions & 0 deletions grade/edit/tree/lib.php
Expand Up @@ -149,6 +149,18 @@ public function build_html_tree($element, $totals, $parents, $level, &$row_count
$actionsmenu->add($icon);
}

if ($this->element_duplicatable($element)) {
$duplicateparams = array();
$duplicateparams['id'] = $COURSE->id;
$duplicateparams['action'] = 'duplicate';
$duplicateparams['eid'] = $eid;
$duplicateparams['sesskey'] = sesskey();
$aurl = new moodle_url('index.php', $duplicateparams);
$duplicateicon = new pix_icon('t/copy', get_string('duplicate'));
$icon = new action_menu_link_secondary($aurl, $duplicateicon, get_string('duplicate'));
$actionsmenu->add($icon);
}

$aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey()));
$moveaction .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move')));
}
Expand Down Expand Up @@ -460,6 +472,24 @@ function element_deletable($element) {
return false;
}

/**
* Given an element of the grade tree, returns whether it is duplicatable or not (only manual grade items are duplicatable)
*
* @param array $element
* @return bool
*/
public function element_duplicatable($element) {
if ($element['type'] != 'item') {
return false;
}

$gradeitem = $element['object'];
if ($gradeitem->itemtype != 'mod') {
return true;
}
return false;
}

/**
* Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param,
* moves the selected items to the requested location. Then redirects the user to the given $returnurl
Expand Down
23 changes: 23 additions & 0 deletions grade/tests/behat/behat_grade.php
Expand Up @@ -117,6 +117,29 @@ public function i_hide_the_grade_item($gradeitem) {
"//tr[descendant::*[text() = " . $this->escape($gradeitem) . "]]", 'xpath_element'));
}

/**
* Duplicates a grade item or category.
*
* Teacher must be on the grade setup page.
*
* @Given /^I duplicate the grade item "(?P<grade_item_string>(?:[^"]|\\")*)"$/
* @param string $gradeitem
*/
public function i_duplicate_the_grade_item($gradeitem) {

$gradeitem = behat_context_helper::escape($gradeitem);

if ($this->running_javascript()) {
$xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
}
}

$this->execute("behat_general::i_click_on_in_the", array(get_string('duplicate'), 'link',
"//tr[descendant::*[text() = " . $this->escape($gradeitem) . "]]", 'xpath_element'));
}

/**
* Sets a calculated manual grade item. Needs a table with item name - idnumber relation.
* The step requires you to be in the 'Gradebook setup' page.
Expand Down
45 changes: 45 additions & 0 deletions grade/tests/behat/grade_item_duplication.feature
@@ -0,0 +1,45 @@
@core @core_grades
Feature: We can duplicate grade items that already exist.
In order to quickly create grade items that have similar settings.
As a teacher
I need to duplicate an existing grade item and check that its values are properly duplicated.

Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "grade categories" exist:
| fullname | course |
| Category1 | C1 |
And the following "activities" exist:
| activity | course | idnumber | name | gradecategory |
| assign | C1 | a1 | Assignment1 | Category1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "grade items" exist:
| itemname | course | category | idnumber | gradetype | grademax | grademin | gradepass | display | decimals | hidden | weightoverride |
| Item1 | C1 | Category1 | 001 | Value | 80.00 | 5.00 | 40.00 | 1 | 1 | 0 | 1 |

Scenario: Ensure the duplicated grade item settings match the original grade item
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Setup > Gradebook setup" in the course gradebook
And I should not see "Duplicate Category1"
And I should not see "Duplicate Assignment1"
When I duplicate the grade item "Item1"
Then I should see "Item1 (copy)"
And I follow "Edit Item1 (copy)"
And the field "Item name" matches value "Item1 (copy)"
And the field "ID number" matches value ""
And the field "Grade type" matches value "Value"
And the field "Maximum grade" matches value "80.00"
And the field "Minimum grade" matches value "5.00"
And the field "Grade to pass" matches value "40.00"
And the field "Grade display type" matches value "Real"
And the field "Overall decimal places" matches value "1"
And the field "Hidden" matches value "0"
And the field "Weight adjusted" matches value "1"
1 change: 1 addition & 0 deletions lang/en/grades.php
Expand Up @@ -163,6 +163,7 @@
$string['droplowestvalues'] = 'Drop {$a} lowest values';
$string['dropxlowest'] = 'Drop X lowest';
$string['dropxlowestwarning'] = 'Note: If you use drop x lowest the grading assumes that all items in the category have the same point value. If point values differ results will be unpredictable';
$string['duplicatedgradeitem'] = '{$a} (copy)';
$string['duplicatescale'] = 'Duplicate scale';
$string['edit'] = 'Edit';
$string['editcalculation'] = 'Edit calculation';
Expand Down
35 changes: 35 additions & 0 deletions lib/grade/grade_item.php
Expand Up @@ -449,6 +449,41 @@ public function delete_all_grades($source=null) {
return true;
}

/**
* Duplicate grade item.
*
* @return grade_item The duplicate grade item
*/
public function duplicate() {
// Convert current object to array.
$copy = (array) $this;

if (empty($copy["id"])) {
throw new moodle_exception('invalidgradeitemid');
}

// Remove fields that will be either unique or automatically filled.
$removekeys = array();
$removekeys[] = 'id';
$removekeys[] = 'idnumber';
$removekeys[] = 'timecreated';
$removekeys[] = 'sortorder';
foreach ($removekeys as $key) {
unset($copy[$key]);
}

// Addendum to name.
$copy["itemname"] = get_string('duplicatedgradeitem', 'grades', $copy["itemname"]);

// Create new grade item.
$gradeitem = new grade_item($copy);

// Insert grade item into database.
$gradeitem->insert();

return $gradeitem;
}

/**
* In addition to perform parent::insert(), calls force_regrading() method too.
*
Expand Down
74 changes: 74 additions & 0 deletions lib/grade/tests/grade_item_test.php
Expand Up @@ -1050,4 +1050,78 @@ protected function sub_test_grade_item_updated_event() {
$this->assertEquals($gradeitem->itemmodule, $event->other['itemmodule']);
$this->assertEquals('updatedname', $event->other['itemname']);
}


/**
* Test grade item duplication expecting success.
*/
public function test_grade_duplicate_grade_item_success() {
$cat = new grade_category();
$cat->courseid = $this->courseid;
$cat->fullname = 'Grade category';
$cat->insert();

// Method exists.
$gi = new grade_item();
$this->assertTrue(method_exists($gi, 'duplicate'));

// Grade item is inserted and valid for duplication.
$gi->courseid = $this->courseid;
$gi->categoryid = $cat->id;
$gi->itemtype = 'manual';
$gi->itemname = 'Grade Item 1';
$gi->idnumber = '1000';
$gi->insert();
$gi2 = $gi->duplicate();

$this->assertEquals($gi->courseid, $gi2->courseid);
$this->assertEquals($gi->categoryid, $gi2->categoryid);
$this->assertEquals($gi->itemtype, $gi2->itemtype);
$this->assertEquals($gi->gradetype, $gi2->gradetype);
$this->assertEquals($gi->grademax, $gi2->grademax);
$this->assertEquals($gi->grademin, $gi2->grademin);
$this->assertEquals($gi->gradepass, $gi2->gradepass);
$this->assertEquals($gi->display, $gi2->display);
$this->assertEquals($gi->decimals, $gi2->decimals);
$this->assertEquals($gi->hidden, $gi2->hidden);
$this->assertEquals($gi->weightoverride, $gi2->weightoverride);

$this->assertNotEquals($gi->id, $gi2->id);
$this->assertNotEquals($gi->idnumber, $gi2->idnumber);
$this->assertNotEquals($gi->sortorder, $gi2->sortorder);
$this->assertNotEquals($gi->itemname, $gi2->itemname);
}

/**
* Test grade item duplication exception expected with incomplete grade item.
*/
public function test_grade_duplicate_grade_item_incomplete() {
// Grade item is not valid because it is empty.
$gi = new grade_item();
$gi->courseid = $this->courseid;
$this->expectException("moodle_exception");
$gi2 = $gi->duplicate();
}

/**
* Test grade item duplication exception expected because item must be in db.
*/
public function test_grade_duplicate_grade_item_not_in_db() {
$cat = new grade_category();
$cat->courseid = $this->courseid;
$cat->fullname = 'Grade category';
$cat->insert();

// Grade item is valid for insertion but is not inserted into db.
// Duplicate method throws an exception.
$gi = new grade_item();
$gi->courseid = $this->courseid;
$gi->categoryid = $cat->id;
$gi->itemtype = 'manual';
$gi->itemname = 'Grade Item 1';
$gi->idnumber = '1000';

$this->expectException("moodle_exception");
$gi2 = $gi->duplicate();
}
}

0 comments on commit 887679e

Please sign in to comment.