Note: You are currently viewing documentation for Moodle 1.9. Up-to-date documentation for the latest stable version is available here: Gradebookplus.

Gradebookplus: Difference between revisions

From MoodleDocs
(Added method for automatic upgrading)
 
(17 intermediate revisions by the same user not shown)
Line 14: Line 14:
=== Migration from GBPv2 to Moodle 1.9 (Experimental) ===
=== Migration from GBPv2 to Moodle 1.9 (Experimental) ===


I'm beginning this thread to discuss how we ought to move toward migrating the GBPv2 data from Moodle 1.8 to 1.9. I have started to play with some SQL queries and figured that sharing my thoughts might be helpful as we work toward this.
The gradebook in Moodle was completely reworked for Moodle 1.9.  GBPv2 does not work with Moodle 1.9 or later and will not be updated to do so because Moodle 1.9 has advanced beyond the level of GBPv2.  The following steps were developed by [[User:Anthony Borrow|Anthony Borrow]] to help users migrating the GBPv2 data from Moodle 1.8 to 1.9. Here is Anthony's disclaimer about the steps:


Please keep in mind that these queries are intended to only show the relationship between the tables so that we can work out a migration plan. They are NOT intended to be run on a production site! My typical warning of using it in so far as it is helpful and avoiding it in so far as it is not helpful doubly applies. With that said, I see the migration as involving 2 steps. Migrating the grade events (1.8) to grade items (1.9) and grade event grades (1.8) to grade grades. What may complicate things or what I am least clear on at this point is moving from grade category (1.8) to grade categories (1.9). I recognize that the current scheme is a bit tenuous.  
:Please keep in mind that these queries are intended to only show the relationship between the tables so that we can work out a migration plan. They are NOT intended to be run on a production site! My typical warning of using it in so far as it is helpful and avoiding it in so far as it is not helpful doubly applies. With that said, I see the migration as involving 2 steps. Migrating the grade events (1.8) to grade items (1.9) and grade event grades (1.8) to grade grades. What may complicate things or what I am least clear on at this point is moving from grade category (1.8) to grade categories (1.9). I recognize that the current scheme is a bit tenuous.
====Let the Moodle upgrader do the work for you====
The first step in moving from Moodle 1.8 to 1.9 is to download and install the latest build of Moodle 1.9.x.  Unfortunately, in the installation process Moodle will drop some of the tables that you need for the migration.  Thus it is necessary to preserve that data in one way or another if you hope to merge it into the gradebook for Moodle 1.9.  The easiest way to preserve that data is to comment out the section of <code>moodle/lib/db/upgrade.php</code> responsible for dropping the tables.  The comment out option is described below in "Step 1", but you can make it even easier on yourself by adding the MySQL commands to <code>moodle/lib/db/upgrade.php</code> so the data conversion occurs automagically and then drops the unnecessary tables.


Here is what I did on my test server with production data from last year. I installed Moodle 1.9 and pointed the install to the 1.8 data. I allowed Moodle 1.9 to completely run through the installation procedure and migrate the data to 1.9. After that, I opened up phpmyadmin and ran the following queries which at the very least did not seem to totally destroy everything (but use with caution and only if you have a good backup copy of your database):
To set up automatic conversion, unzip the latest version of Moodle 1.9.x and open the new <code>moodle/lib/db/upgrade.php</code> file in your favorite text editor. Find the code indicated in the table below and replace it with the replacement code listed. Save the file, make sure you have backed up your MySQL database, copied <code>moodle/config.php</code> from your previous Moodle folder into the new Moodle folder, and changed the names of the folders so the new Moodle folder has the name Moodle will be looking for.  Finally, using your web browser go to <code><your moodle path>/admin</code> to begin the upgrade process.


-- As mentioned, the primary challenge is to make sure that the categoryid is correct
Text to be replaced in <code>moodle/lib/db/upgrade.php</code> (approximately lines 2555 - 2570):
<pre>/// drop old gradebook tables
    if ($result && $oldversion < 2007100903) {
        $tables = array('grade_category',
                        'grade_item',
                        'grade_letter',
                        'grade_preferences',
                        'grade_exceptions');


-- add all of the grade_events to grade_items
        foreach ($tables as $table) {
            $table = new XMLDBTable($table);
            if (table_exists($table)) {
                drop_table($table);
            }
        }


INSERT INTO mdl_grade_items (courseid, categoryid, itemname, itemtype, iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype, ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax, 0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified FROM mdl_grade_item as gi, mdl_grade_events as ge WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id;
        upgrade_main_savepoint($result, 2007100903);
    }
</pre>


-- replace the old categoryid with the new categoryid (where possible)
Text to put in its place:
<pre>///Convert GradebookPlus v2 data, then drop old gradebook tables
    if ($result && $oldversion < 2007100903) {


UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_category as gc, mdl_grade_categories as gcs) ON (gi.categoryid=gc.id AND gi.courseid=gcs.courseid AND gc.name=gcs.fullname)
        {
SET gi.categoryid = gcs.id, gi.idnumber=replace(gi.idnumber,'GBE','GBI')
            ///Add all of the grade_events to grade_items
WHERE gi.idnumber LIKE 'GBE%';
            $sql = "INSERT INTO {$CFG->prefix}grade_items (courseid, categoryid, itemname, itemtype,
                iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid
                as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype,
                ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax,  
                0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified
                FROM {$CFG->prefix}grade_item as gi, {$CFG->prefix}grade_events as ge
                WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id";
            execute_sql($sql);


-- assign a generic categoryid for any thing that was not categorized (it is presumed that the grade event was not previously categorised in the course it was in)
            ///Replace the old categoryid with the new categoryid (where possible)
            $sql = "UPDATE {$CFG->prefix}grade_items as gi LEFT JOIN ({$CFG->prefix}grade_category
                as gc) ON (gi.categoryid=gc.id) LEFT JOIN ({$CFG->prefix}grade_categories as gcs)
                ON (gi.courseid=gcs.courseid AND gc.name=gcs.fullname) SET gi.categoryid = gcs.id,
                gi.idnumber=replace(gi.idnumber,'GBE','GBI') WHERE gi.idnumber LIKE 'GBE%'";
            execute_sql($sql);


UPDATE mdl_grade_items as gi  
            ///Assign a generic categoryid for any thing that was not categorized
LEFT JOIN (mdl_grade_categories as gcs)  
            $sql = "UPDATE {$CFG->prefix}grade_items as gi LEFT JOIN ({$CFG->prefix}grade_categories
ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL)
                as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL)  
SET gi.categoryid = gcs.id
                SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL";
WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;
            execute_sql($sql);
            ///Double-checking categories
            $sql = "INSERT INTO {$CFG->prefix}grade_categories (courseid, depth, fullname,
                timecreated, timemodified) SELECT DISTINCT courseid, 1 as depth,
                'Course grade category' as fullname, unix_timestamp() as timecreated,
                unix_timestamp() as timemodified FROM {$CFG->prefix}grade_items WHERE idnumber
                LIKE 'GBI%' AND categoryid IS NULL";
            execute_sql($sql);


-- I still had 2 items that were not categorised and would suggest creating the records in mdl_grade_categories and re-running the query above.
            ///Add the path
            $sql = "UPDATE {$CFG->prefix}grade_categories SET path = concat('/',id,'/') WHERE path
                IS NULL AND depth=1";
            execute_sql($sql);


INSERT INTO mdl_grade_categories (courseid, depth, fullname, timecreated, timemodified)
            ///Populate the previously NULL categoryids
SELECT DISTINCT courseid, 1 as depth, 'Course grade category' as fullname, unix_timestamp() as timecreated, unix_timestamp() as timemodified
            $sql = "UPDATE {$CFG->prefix}grade_items as gi LEFT JOIN ({$CFG->prefix}grade_categories
FROM mdl_grade_items
                as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid =
WHERE idnumber LIKE 'GBI%' AND categoryid IS NULL;
                gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL";
            execute_sql($sql);


-- add the path - there may be a way to do this above but I do not know how to do it with the auto incrementing id field so I let it first insert the record and then I use the data
            ///Copy scores from old tables into new tables
            $sql = "INSERT INTO {$CFG->prefix}grade_grades (itemid, userid, usermodified, finalgrade,
                timecreated, timemodified) SELECT gi.id as itemid, geg.userid as userid, geg.teacher
                as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated,
                geg.timemarked as timemodified FROM {$CFG->prefix}grade_events_grades as geg,
                {$CFG->prefix}grade_items as gi WHERE replace(gi.idnumber,'GBI-','')=geg.event";
            execute_sql($sql);


UPDATE mdl_grade_categories
            ///Set aggregation within each category to simple weighted mean
SET path = concat('/',id,'/')
            $sql = "UPDATE {$CFG->prefix}grade_categories SET aggregation = 11 WHERE depth != 1";
WHERE path IS NULL AND depth=1;
            execute_sql($sql);
        }
       
        $tables = array('grade_category',
                        'grade_item',
                        'grade_letter',
                        'grade_preferences',
                        'grade_exceptions');


-- populate the previously NULL categoryids
        foreach ($tables as $table) {
            $table = new XMLDBTable($table);
            if (table_exists($table)) {
                drop_table($table);
            }
        }


UPDATE mdl_grade_items as gi  
        upgrade_main_savepoint($result, 2007100903);
LEFT JOIN (mdl_grade_categories as gcs)  
    }
ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL)
</pre>
SET gi.categoryid = gcs.id
 
WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;
That should do it!  Do be aware, however, that the new ability to ignore blank grades will change averages if it is used.  Also, extra credit is pretty much broken so extra credit assignments and assignments with scores in excess of 100% will now affect the grades differently.
====Step 1--Install Moodle 1.9====
If you prefer to do the conversion manually, you must first preserve your GBPv2 data through the upgrade process.  Before you log into Moodle with your web browser (to start the upgrade process) edit <code>moodle/lib/db/upgrade.php</code> and comment out (or delete if you prefer) the lines that drop the gradebook tables quoted below.  They should be approximately lines 2555 - 2570:
<pre>    if ($result && $oldversion < 2007100903) {
        $tables = array('grade_category',
                        'grade_item',
                        'grade_letter',
                        'grade_preferences',
                        'grade_exceptions');
 
        foreach ($tables as $table) {
            $table = new XMLDBTable($table);
            if (table_exists($table)) {
                drop_table($table);
            }
        }
 
        upgrade_main_savepoint($result, 2007100903);
    }
</pre>
After making the edit, complete your installation (the conversion of files that Moodle does this the first time you log in after installing the new files) and move on to step 2.
 
====Step 2--Convert Database with MySQL Commands====
Using your favorite MySQL tool (I recommend installing [http://moodle.org/mod/data/view.php?d=13&rid=448 MySQL Admin] which is [http://www.phpmyadmin.net/home_page/index.php phpMyAdmin] adapted for Moodle) run each of the following commands on your Moodle database.  Note that the commands below assume that you used mdl_ as the prefix for your Moodle tables.  If you did not, then you will need to change all table names accordingly.  At the end of this document, all of these commands are placed together so you can copy and paste the entire set of commands in one action.
 
=====Add all of the grade_events to grade_items=====
 
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>INSERT INTO mdl_grade_items (courseid, categoryid, itemname, itemtype, iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype, ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax, 0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified FROM mdl_grade_item as gi, mdl_grade_events as ge WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id;</nowiki>
|-
|}
 
=====Replace the old categoryid with the new categoryid (where possible)=====
 
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_category as gc) ON (gi.categoryid=gc.id) LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gc.name=gcs.fullname) SET gi.categoryid = gcs.id, gi.idnumber=replace(gi.idnumber,'GBE','GBI') WHERE gi.idnumber LIKE 'GBE%';</nowiki>
|-
|}
 
=====Assign a generic categoryid for any thing that was not categorized (it is presumed that the grade event was not previously categorised in the course it was in)=====
 
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>UPDATE mdl_grade_items as gi  LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;</nowiki>
|-
|}
 
-- I still had 2 items that were not categorised and would suggest creating the records in mdl_grade_categories and re-running the query above
 
=====Double-checking categories=====
 
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>INSERT INTO mdl_grade_categories (courseid, depth, fullname, timecreated, timemodified) SELECT DISTINCT courseid, 1 as depth, 'Course grade category' as fullname, unix_timestamp() as timecreated, unix_timestamp() as timemodified FROM mdl_grade_items WHERE idnumber LIKE 'GBI%' AND categoryid IS NULL;</nowiki>
|-
|}
 
=====Add the path=====
-- There may be a way to do this above but I do not know how to do it with the auto incrementing id field so I let it first insert the record and then I use the data
 
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>UPDATE mdl_grade_categories SET path = concat('/',id,'/') WHERE path IS NULL AND depth=1;</nowiki>
|-
|}
 
=====Populate the previously NULL categoryids=====
 
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>UPDATE mdl_grade_items as gi  LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;</nowiki>
|-
|}


-- I have used the grade event (1.8) id field concatenated with the GBE- string as the id number to easily identify the items as GBP items. After assigning a category the idnumber becomes GBI- even if it is NULL. This will also facilitate linking the items (1.9) and assigning the grades from events_grades to grade_grades.
-- I have used the grade event (1.8) id field concatenated with the GBE- string as the id number to easily identify the items as GBP items. After assigning a category the idnumber becomes GBI- even if it is NULL. This will also facilitate linking the items (1.9) and assigning the grades from events_grades to grade_grades.
-- OK - now that hopefully all of the grade events have been turned into grade items and the new categories have been found or assigned let us go ahead and attempt to pull the grades over.
-- OK - now that hopefully all of the grade events have been turned into grade items and the new categories have been found or assigned let us go ahead and attempt to pull the grades over.


INSERT INTO mdl_grade_grades (itemid, userid, usermodified, finalgrade, timecreated, timemodified)  
=====Copy scores from old tables into new tables=====
SELECT gi.id as itemid, geg.userid as userid, geg.teacher as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated, geg.timemarked as timemodified
 
FROM mdl_grade_events_grades as geg, mdl_grade_items as gi  
:{| border="1"
WHERE replace(gi.idnumber,'GBI-','')=geg.event;
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>INSERT INTO mdl_grade_grades (itemid, userid, usermodified, finalgrade, timecreated, timemodified) SELECT gi.id as itemid, geg.userid as userid, geg.teacher as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated, geg.timemarked as timemodified FROM mdl_grade_events_grades as geg, mdl_grade_items as gi WHERE replace(gi.idnumber,'GBI-','')=geg.event;</nowiki>
|-
|}


I have tested these queries on production data on a test server and it seems to work for me; however, I suspect there are other cases out there for which this may not be a total solution but my hope is that it is at least a step in the right direction. For those who are chomping at the bit, if you could test this with some production data on a test server and report back that would be most helpful. I appreciate any feedback, questions, critiques, etc. Peace - Anthony
I have tested these queries on production data on a test server and it seems to work for me; however, I suspect there are other cases out there for which this may not be a total solution but my hope is that it is at least a step in the right direction. For those who are chomping at the bit, if you could test this with some production data on a test server and report back that would be most helpful. I appreciate any feedback, questions, critiques, etc. Peace - Anthony
=====Issues with grade aggregation=====
Grade aggregation is fine if teachers selected to "Use Percent" to assign letter grade--all assignments and categories will be switched to "Simple Mean of Grades" and the calculated grade will be unchanged.  However, if teachers selected "Use Weight" to assign letter grade, all assignments and categories for the class will be set to "Weighted Mean of Grades" but all assignments will have a null weighting factor.
Based on the original suggestion by [http://moodle.org/user/view.php?id=5845&course=5 Bob Puffer], the following should fix grade aggregation problems:
First, set all assignments and categories that are NOT the course grade to "Simple Weighted Mean of Grade" with the MySQL command below:
:{| border="1"
|-
|style="background:azure; color:black" format modifier (not displayed)|<nowiki>UPDATE mdl_grade_categories SET aggregation = 11 WHERE depth != 1;</nowiki>
|-
|}
It is now necessary to force Moodle to reaggregate grades.  This can be accomplished with the following steps:
# Go into '''Site Administration->Grades->General Settings'''
# Toggle the "Include Scales in Aggregation" checkbox on or off
# Save Changes. It will take a while for grades in the system to recalculate.
# Toggle the "Include Scales in Aggregation" back to its original position before step 3
# Save Changes again.
====Summary of SQL Commands for Migration from GBPv2 in Moodle 1.8 to Moodle 1.9====
For the brave (but not brave enough to let upgrade.php do the work for you), here are all of the SQL commands combined so you can copy and paste just once:
<pre>INSERT INTO mdl_grade_items (courseid, categoryid, itemname, itemtype, iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype, ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax, 0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified FROM mdl_grade_item as gi, mdl_grade_events as ge WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id;
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_category as gc) ON (gi.categoryid=gc.id) LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gc.name=gcs.fullname) SET gi.categoryid = gcs.id, gi.idnumber=replace(gi.idnumber,'GBE','GBI') WHERE gi.idnumber LIKE 'GBE%';
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;
INSERT INTO mdl_grade_categories (courseid, depth, fullname, timecreated, timemodified) SELECT DISTINCT courseid, 1 as depth, 'Course grade category' as fullname, unix_timestamp() as timecreated, unix_timestamp() as timemodified FROM mdl_grade_items WHERE idnumber LIKE 'GBI%' AND categoryid IS NULL;
UPDATE mdl_grade_categories SET path = concat('/',id,'/') WHERE path IS NULL AND depth=1;
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;
INSERT INTO mdl_grade_grades (itemid, userid, usermodified, finalgrade, timecreated, timemodified) SELECT gi.id as itemid, geg.userid as userid, geg.teacher as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated, geg.timemarked as timemodified FROM mdl_grade_events_grades as geg, mdl_grade_items as gi WHERE replace(gi.idnumber,'GBI-','')=geg.event;
UPDATE mdl_grade_categories SET aggregation = 11 WHERE depth != 1;
</pre>

Latest revision as of 03:09, 1 August 2008

The GradebookPlus Version 2 (GBPv2) patch adds the following functionality to standard gradebook:

  • in-place editing of gradebook
  • multi-column editing (spreadheet-style) for all numerically-marked activities in a given category
  • create graded events that are not course activities (this gets around the issue in the gradebook where users had to create offline assignments for non-Moodle course work).
  • Works with Moodle versions 1.5 and 1.6
18STABLE branch works with Moodle 1.8 (and probably Moodle 1.7). Efforts are made to merge changes from 18STABLE branch of Moodle's standard gradebook with the GBPv2 patch 18STABLE branch. Anthony hopes that the GBPv2 patch will be made obsolete by the improvements and new features made available in Moodle 1.9. Current plans are to fix bugs in the GBPv2 patch as they are identified and reported in the CONTRIB section of the Moodle Tracker.

More information about the GBPv2 Patch is available at: http://moodle.org/mod/data/view.php?d=13&rid=929

Migration from GBPv2 to Moodle 1.9 (Experimental)

The gradebook in Moodle was completely reworked for Moodle 1.9. GBPv2 does not work with Moodle 1.9 or later and will not be updated to do so because Moodle 1.9 has advanced beyond the level of GBPv2. The following steps were developed by Anthony Borrow to help users migrating the GBPv2 data from Moodle 1.8 to 1.9. Here is Anthony's disclaimer about the steps:

Please keep in mind that these queries are intended to only show the relationship between the tables so that we can work out a migration plan. They are NOT intended to be run on a production site! My typical warning of using it in so far as it is helpful and avoiding it in so far as it is not helpful doubly applies. With that said, I see the migration as involving 2 steps. Migrating the grade events (1.8) to grade items (1.9) and grade event grades (1.8) to grade grades. What may complicate things or what I am least clear on at this point is moving from grade category (1.8) to grade categories (1.9). I recognize that the current scheme is a bit tenuous.

Let the Moodle upgrader do the work for you

The first step in moving from Moodle 1.8 to 1.9 is to download and install the latest build of Moodle 1.9.x. Unfortunately, in the installation process Moodle will drop some of the tables that you need for the migration. Thus it is necessary to preserve that data in one way or another if you hope to merge it into the gradebook for Moodle 1.9. The easiest way to preserve that data is to comment out the section of moodle/lib/db/upgrade.php responsible for dropping the tables. The comment out option is described below in "Step 1", but you can make it even easier on yourself by adding the MySQL commands to moodle/lib/db/upgrade.php so the data conversion occurs automagically and then drops the unnecessary tables.

To set up automatic conversion, unzip the latest version of Moodle 1.9.x and open the new moodle/lib/db/upgrade.php file in your favorite text editor. Find the code indicated in the table below and replace it with the replacement code listed. Save the file, make sure you have backed up your MySQL database, copied moodle/config.php from your previous Moodle folder into the new Moodle folder, and changed the names of the folders so the new Moodle folder has the name Moodle will be looking for. Finally, using your web browser go to <your moodle path>/admin to begin the upgrade process.

Text to be replaced in moodle/lib/db/upgrade.php (approximately lines 2555 - 2570):

/// drop old gradebook tables
    if ($result && $oldversion < 2007100903) {
        $tables = array('grade_category',
                        'grade_item',
                        'grade_letter',
                        'grade_preferences',
                        'grade_exceptions');

        foreach ($tables as $table) {
            $table = new XMLDBTable($table);
            if (table_exists($table)) {
                drop_table($table);
            }
        }

        upgrade_main_savepoint($result, 2007100903);
    }

Text to put in its place:

///Convert GradebookPlus v2 data, then drop old gradebook tables
    if ($result && $oldversion < 2007100903) {

        {
            ///Add all of the grade_events to grade_items
            $sql = "INSERT INTO {$CFG->prefix}grade_items (courseid, categoryid, itemname, itemtype, 
                iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid 
                as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype, 
                ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax, 
                0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified 
                FROM {$CFG->prefix}grade_item as gi, {$CFG->prefix}grade_events as ge 
                WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id"; 
            execute_sql($sql);

            ///Replace the old categoryid with the new categoryid (where possible) 
            $sql = "UPDATE {$CFG->prefix}grade_items as gi LEFT JOIN ({$CFG->prefix}grade_category 
                as gc) ON (gi.categoryid=gc.id) LEFT JOIN ({$CFG->prefix}grade_categories as gcs) 
                ON (gi.courseid=gcs.courseid AND gc.name=gcs.fullname) SET gi.categoryid = gcs.id, 
                gi.idnumber=replace(gi.idnumber,'GBE','GBI') WHERE gi.idnumber LIKE 'GBE%'"; 
            execute_sql($sql);

            ///Assign a generic categoryid for any thing that was not categorized 
            $sql = "UPDATE {$CFG->prefix}grade_items as gi LEFT JOIN ({$CFG->prefix}grade_categories 
                as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) 
                SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL"; 
            execute_sql($sql);
 
            ///Double-checking categories
            $sql = "INSERT INTO {$CFG->prefix}grade_categories (courseid, depth, fullname, 
                timecreated, timemodified) SELECT DISTINCT courseid, 1 as depth, 
                'Course grade category' as fullname, unix_timestamp() as timecreated, 
                unix_timestamp() as timemodified FROM {$CFG->prefix}grade_items WHERE idnumber 
                LIKE 'GBI%' AND categoryid IS NULL"; 
            execute_sql($sql);

            ///Add the path 
            $sql = "UPDATE {$CFG->prefix}grade_categories SET path = concat('/',id,'/') WHERE path 
                IS NULL AND depth=1"; 
            execute_sql($sql);

            ///Populate the previously NULL categoryids
            $sql = "UPDATE {$CFG->prefix}grade_items as gi LEFT JOIN ({$CFG->prefix}grade_categories 
                as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = 
                gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL"; 
            execute_sql($sql);

            ///Copy scores from old tables into new tables 
            $sql = "INSERT INTO {$CFG->prefix}grade_grades (itemid, userid, usermodified, finalgrade, 
                timecreated, timemodified) SELECT gi.id as itemid, geg.userid as userid, geg.teacher 
                as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated, 
                geg.timemarked as timemodified FROM {$CFG->prefix}grade_events_grades as geg, 
                {$CFG->prefix}grade_items as gi WHERE replace(gi.idnumber,'GBI-','')=geg.event"; 
            execute_sql($sql);

            ///Set aggregation within each category to simple weighted mean
            $sql = "UPDATE {$CFG->prefix}grade_categories SET aggregation = 11 WHERE depth != 1"; 
            execute_sql($sql);
        }
        
        $tables = array('grade_category',
                        'grade_item',
                        'grade_letter',
                        'grade_preferences',
                        'grade_exceptions');

        foreach ($tables as $table) {
            $table = new XMLDBTable($table);
            if (table_exists($table)) {
                drop_table($table);
            }
        }

        upgrade_main_savepoint($result, 2007100903);
    }

That should do it! Do be aware, however, that the new ability to ignore blank grades will change averages if it is used. Also, extra credit is pretty much broken so extra credit assignments and assignments with scores in excess of 100% will now affect the grades differently.

Step 1--Install Moodle 1.9

If you prefer to do the conversion manually, you must first preserve your GBPv2 data through the upgrade process. Before you log into Moodle with your web browser (to start the upgrade process) edit moodle/lib/db/upgrade.php and comment out (or delete if you prefer) the lines that drop the gradebook tables quoted below. They should be approximately lines 2555 - 2570:

    if ($result && $oldversion < 2007100903) {
        $tables = array('grade_category',
                        'grade_item',
                        'grade_letter',
                        'grade_preferences',
                        'grade_exceptions');

        foreach ($tables as $table) {
            $table = new XMLDBTable($table);
            if (table_exists($table)) {
                drop_table($table);
            }
        }

        upgrade_main_savepoint($result, 2007100903);
    }

After making the edit, complete your installation (the conversion of files that Moodle does this the first time you log in after installing the new files) and move on to step 2.

Step 2--Convert Database with MySQL Commands

Using your favorite MySQL tool (I recommend installing MySQL Admin which is phpMyAdmin adapted for Moodle) run each of the following commands on your Moodle database. Note that the commands below assume that you used mdl_ as the prefix for your Moodle tables. If you did not, then you will need to change all table names accordingly. At the end of this document, all of these commands are placed together so you can copy and paste the entire set of commands in one action.

Add all of the grade_events to grade_items
INSERT INTO mdl_grade_items (courseid, categoryid, itemname, itemtype, iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype, ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax, 0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified FROM mdl_grade_item as gi, mdl_grade_events as ge WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id;
Replace the old categoryid with the new categoryid (where possible)
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_category as gc) ON (gi.categoryid=gc.id) LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gc.name=gcs.fullname) SET gi.categoryid = gcs.id, gi.idnumber=replace(gi.idnumber,'GBE','GBI') WHERE gi.idnumber LIKE 'GBE%';
Assign a generic categoryid for any thing that was not categorized (it is presumed that the grade event was not previously categorised in the course it was in)
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;

-- I still had 2 items that were not categorised and would suggest creating the records in mdl_grade_categories and re-running the query above

Double-checking categories
INSERT INTO mdl_grade_categories (courseid, depth, fullname, timecreated, timemodified) SELECT DISTINCT courseid, 1 as depth, 'Course grade category' as fullname, unix_timestamp() as timecreated, unix_timestamp() as timemodified FROM mdl_grade_items WHERE idnumber LIKE 'GBI%' AND categoryid IS NULL;
Add the path

-- There may be a way to do this above but I do not know how to do it with the auto incrementing id field so I let it first insert the record and then I use the data

UPDATE mdl_grade_categories SET path = concat('/',id,'/') WHERE path IS NULL AND depth=1;
Populate the previously NULL categoryids
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL;

-- I have used the grade event (1.8) id field concatenated with the GBE- string as the id number to easily identify the items as GBP items. After assigning a category the idnumber becomes GBI- even if it is NULL. This will also facilitate linking the items (1.9) and assigning the grades from events_grades to grade_grades. -- OK - now that hopefully all of the grade events have been turned into grade items and the new categories have been found or assigned let us go ahead and attempt to pull the grades over.

Copy scores from old tables into new tables
INSERT INTO mdl_grade_grades (itemid, userid, usermodified, finalgrade, timecreated, timemodified) SELECT gi.id as itemid, geg.userid as userid, geg.teacher as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated, geg.timemarked as timemodified FROM mdl_grade_events_grades as geg, mdl_grade_items as gi WHERE replace(gi.idnumber,'GBI-','')=geg.event;

I have tested these queries on production data on a test server and it seems to work for me; however, I suspect there are other cases out there for which this may not be a total solution but my hope is that it is at least a step in the right direction. For those who are chomping at the bit, if you could test this with some production data on a test server and report back that would be most helpful. I appreciate any feedback, questions, critiques, etc. Peace - Anthony

Issues with grade aggregation

Grade aggregation is fine if teachers selected to "Use Percent" to assign letter grade--all assignments and categories will be switched to "Simple Mean of Grades" and the calculated grade will be unchanged. However, if teachers selected "Use Weight" to assign letter grade, all assignments and categories for the class will be set to "Weighted Mean of Grades" but all assignments will have a null weighting factor.

Based on the original suggestion by Bob Puffer, the following should fix grade aggregation problems:

First, set all assignments and categories that are NOT the course grade to "Simple Weighted Mean of Grade" with the MySQL command below:

UPDATE mdl_grade_categories SET aggregation = 11 WHERE depth != 1;

It is now necessary to force Moodle to reaggregate grades. This can be accomplished with the following steps:

  1. Go into Site Administration->Grades->General Settings
  2. Toggle the "Include Scales in Aggregation" checkbox on or off
  3. Save Changes. It will take a while for grades in the system to recalculate.
  4. Toggle the "Include Scales in Aggregation" back to its original position before step 3
  5. Save Changes again.

Summary of SQL Commands for Migration from GBPv2 in Moodle 1.8 to Moodle 1.9

For the brave (but not brave enough to let upgrade.php do the work for you), here are all of the SQL commands combined so you can copy and paste just once:

INSERT INTO mdl_grade_items (courseid, categoryid, itemname, itemtype, iteminfo, idnumber, grademax, grademin, timecreated, timemodified) SELECT gi.courseid as courseid, gi.category as categoryid, ge.name as itemname, 'manual' as itemtype, ge.description as iteminfo, concat('GBE-',ge.id) as idnumber, ge.grade as grademax, 0 as grademin, ge.timemodified as timecreated, ge.timemodified as timemodified FROM mdl_grade_item as gi, mdl_grade_events as ge WHERE gi.courseid=ge.course AND gi.modid=0 AND gi.cminstance=ge.id; 
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_category as gc) ON (gi.categoryid=gc.id) LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gc.name=gcs.fullname) SET gi.categoryid = gcs.id, gi.idnumber=replace(gi.idnumber,'GBE','GBI') WHERE gi.idnumber LIKE 'GBE%';
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL; 
INSERT INTO mdl_grade_categories (courseid, depth, fullname, timecreated, timemodified) SELECT DISTINCT courseid, 1 as depth, 'Course grade category' as fullname, unix_timestamp() as timecreated, unix_timestamp() as timemodified FROM mdl_grade_items WHERE idnumber LIKE 'GBI%' AND categoryid IS NULL; 
UPDATE mdl_grade_categories SET path = concat('/',id,'/') WHERE path IS NULL AND depth=1;
UPDATE mdl_grade_items as gi LEFT JOIN (mdl_grade_categories as gcs) ON (gi.courseid=gcs.courseid AND gcs.parent IS NULL) SET gi.categoryid = gcs.id WHERE gi.idnumber LIKE 'GBI%' AND gi.categoryid IS NULL; 
INSERT INTO mdl_grade_grades (itemid, userid, usermodified, finalgrade, timecreated, timemodified) SELECT gi.id as itemid, geg.userid as userid, geg.teacher as usermodified, geg.grade as finalgrade, geg.timemarked as timecreated, geg.timemarked as timemodified FROM mdl_grade_events_grades as geg, mdl_grade_items as gi WHERE replace(gi.idnumber,'GBI-','')=geg.event; 
UPDATE mdl_grade_categories SET aggregation = 11 WHERE depth != 1;