diff --git a/backup/converter/moodle1/handlerlib.php b/backup/converter/moodle1/handlerlib.php index 5c15bc279cb..dc10857a91f 100644 --- a/backup/converter/moodle1/handlerlib.php +++ b/backup/converter/moodle1/handlerlib.php @@ -52,6 +52,8 @@ abstract class moodle1_handlers_factory { new moodle1_roles_definition_handler($converter), new moodle1_question_bank_handler($converter), new moodle1_scales_handler($converter), + new moodle1_outcomes_handler($converter), + new moodle1_gradebook_handler($converter), ); $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter)); @@ -927,10 +929,23 @@ class moodle1_course_outline_handler extends moodle1_xml_handler { $this->write_xml('module', $cminfo, array('/module/id', '/module/version')); $this->close_xml_writer(); - // todo: write proper grades.xml and roles.xml, for now we just make - // sure that those files are present + // write grades.xml + $this->open_xml_writer($directory.'/grades.xml'); + $this->xmlwriter->begin_tag('activity_gradebook'); + $gradeitems = $this->converter->get_stash_or_default('gradebook_modgradeitem_'.$modname, $modinstanceid, array()); + if (!empty($gradeitems)) { + $this->xmlwriter->begin_tag('grade_items'); + foreach ($gradeitems as $gradeitem) { + $this->write_xml('grade_item', $gradeitem, array('/grade_item/id')); + } + $this->xmlwriter->end_tag('grade_items'); + } + $this->write_xml('grade_letters', array()); // no grade_letters in module context in Moodle 1.9 + $this->xmlwriter->end_tag('activity_gradebook'); + $this->close_xml_writer(); + + // todo: write proper roles.xml, for now we just make sure the file is present $this->make_sure_xml_exists($directory.'/roles.xml', 'roles'); - $this->make_sure_xml_exists($directory.'/grades.xml', 'activity_gradebook'); } } } @@ -1319,7 +1334,7 @@ class moodle1_scales_handler extends moodle1_handler { protected $fileman = null; /** - * Registers path that are not qtype-specific + * Registers paths */ public function get_paths() { return array( @@ -1372,6 +1387,272 @@ class moodle1_scales_handler extends moodle1_handler { } +/** + * Handles the conversion of the outcomes + */ +class moodle1_outcomes_handler extends moodle1_xml_handler { + + /** @var moodle1_file_manager instance used to convert images embedded into outcome descriptions */ + protected $fileman = null; + + /** + * Registers paths + */ + public function get_paths() { + return array( + new convert_path('gradebook_grade_outcomes', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES'), + new convert_path( + 'gradebook_grade_outcome', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES/GRADE_OUTCOME', + array( + 'addfields' => array( + 'descriptionformat' => FORMAT_MOODLE, + ), + ) + ), + ); + } + + /** + * Prepares the file manager and starts writing outcomes.xml + */ + public function on_gradebook_grade_outcomes_start() { + + $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM); + $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'outcome'); + + $this->open_xml_writer('outcomes.xml'); + $this->xmlwriter->begin_tag('outcomes_definition'); + } + + /** + * Processes GRADE_OUTCOME tags progressively + */ + public function process_gradebook_grade_outcome(array $data, array $raw) { + global $CFG; + + // replay the upgrade step 2009110400 + if ($CFG->texteditors !== 'textarea') { + $data['description'] = text_to_html($data['description'], false, false, true); + $data['descriptionformat'] = FORMAT_HTML; + } + + // convert course files embedded into the outcome description field + $this->fileman->itemid = $data['id']; + $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman); + + // write the outcome data + $this->write_xml('outcome', $data, array('/outcome/id')); + + return $data; + } + + /** + * Closes outcomes.xml + */ + public function on_gradebook_grade_outcomes_end() { + $this->xmlwriter->end_tag('outcomes_definition'); + $this->close_xml_writer(); + } +} + + +/** + * Handles the conversion of the gradebook structures in the moodle.xml file + */ +class moodle1_gradebook_handler extends moodle1_xml_handler { + + /** @var array of (int)gradecategoryid => (int|null)parentcategoryid */ + protected $categoryparent = array(); + + /** + * Registers paths + */ + public function get_paths() { + return array( + new convert_path('gradebook', '/MOODLE_BACKUP/COURSE/GRADEBOOK'), + new convert_path('gradebook_grade_letter', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_LETTERS/GRADE_LETTER'), + new convert_path( + 'gradebook_grade_category', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_CATEGORIES/GRADE_CATEGORY', + array( + 'addfields' => array( + 'hidden' => 0, // upgrade step 2010011200 + ), + ) + ), + new convert_path('gradebook_grade_item', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM'), + new convert_path('gradebook_grade_item_grades', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM/GRADE_GRADES'), + ); + } + + /** + * Initializes the in-memory structures + * + * This should not be needed actually as the moodle.xml contains just one GRADEBOOK + * element. But who knows - maybe someone will want to write a mass conversion + * tool in the future (not me definitely ;-) + */ + public function on_gradebook_start() { + $this->categoryparent = array(); + } + + /** + * Processes one GRADE_LETTER data + * + * In Moodle 1.9, all grade_letters are from course context only. Therefore + * we put them here. + */ + public function process_gradebook_grade_letter(array $data, array $raw) { + $this->converter->set_stash('gradebook_gradeletter', $data, $data['id']); + } + + /** + * Processes one GRADE_CATEGORY data + */ + public function process_gradebook_grade_category(array $data, array $raw) { + $this->categoryparent[$data['id']] = $data['parent']; + $this->converter->set_stash('gradebook_gradecategory', $data, $data['id']); + } + + /** + * Processes one GRADE_ITEM data + */ + public function process_gradebook_grade_item(array $data, array $raw) { + + // here we use get_nextid() to get a nondecreasing sequence + $data['sortorder'] = $this->converter->get_nextid(); + + if ($data['itemtype'] === 'mod') { + return $this->process_mod_grade_item($data, $raw); + + } else if (in_array($data['itemtype'], array('manual', 'course', 'category'))) { + return $this->process_nonmod_grade_item($data, $raw); + + } else { + $this->log('unsupported grade_item type', backup::LOG_ERROR, $data['itemtype']); + } + } + + /** + * Processes one GRADE_ITEM of the type 'mod' + */ + protected function process_mod_grade_item(array $data, array $raw) { + + $stashname = 'gradebook_modgradeitem_'.$data['itemmodule']; + $stashitemid = $data['iteminstance']; + $gradeitems = $this->converter->get_stash_or_default($stashname, $stashitemid, array()); + + // typically there will be single item with itemnumber 0 + $gradeitems[$data['itemnumber']] = $data; + + $this->converter->set_stash($stashname, $gradeitems, $stashitemid); + + return $data; + } + + /** + * Processes one GRADE_ITEM of te type 'manual' or 'course' or 'category' + */ + protected function process_nonmod_grade_item(array $data, array $raw) { + + $stashname = 'gradebook_nonmodgradeitem'; + $stashitemid = $data['id']; + $this->converter->set_stash($stashname, $data, $stashitemid); + + return $data; + } + + /** + * @todo + */ + public function on_gradebook_grade_item_grades_start() { + } + + /** + * Writes the collected information into gradebook.xml + */ + public function on_gradebook_end() { + + $this->open_xml_writer('gradebook.xml'); + $this->xmlwriter->begin_tag('gradebook'); + $this->write_grade_categories(); + $this->write_grade_items(); + $this->write_grade_letters(); + $this->xmlwriter->end_tag('gradebook'); + $this->close_xml_writer(); + } + + /** + * Writes grade_categories + */ + protected function write_grade_categories() { + + $this->xmlwriter->begin_tag('grade_categories'); + foreach ($this->converter->get_stash_itemids('gradebook_gradecategory') as $gradecategoryid) { + $gradecategory = $this->converter->get_stash('gradebook_gradecategory', $gradecategoryid); + $path = $this->calculate_category_path($gradecategoryid); + $gradecategory['depth'] = count($path); + $gradecategory['path'] = '/'.implode('/', $path).'/'; + $this->write_xml('grade_category', $gradecategory, array('/grade_category/id')); + } + $this->xmlwriter->end_tag('grade_categories'); + } + + /** + * Calculates the path to the grade_category + * + * Moodle 1.9 backup does not store the grade_category's depth and path. This method is used + * to repopulate this information using the $this->categoryparent values. + * + * @param int $categoryid + * @return array of ids including the categoryid + */ + protected function calculate_category_path($categoryid) { + + if (!array_key_exists($categoryid, $this->categoryparent)) { + throw new moodle1_convert_exception('gradebook_unknown_categoryid', null, $categoryid); + } + + $path = array($categoryid); + $parent = $this->categoryparent[$categoryid]; + while (!is_null($parent)) { + array_unshift($path, $parent); + $parent = $this->categoryparent[$parent]; + if (in_array($parent, $path)) { + throw new moodle1_convert_exception('circular_reference_in_categories_tree'); + } + } + + return $path; + } + + /** + * Writes grade_items + */ + protected function write_grade_items() { + + $this->xmlwriter->begin_tag('grade_items'); + foreach ($this->converter->get_stash_itemids('gradebook_nonmodgradeitem') as $gradeitemid) { + $gradeitem = $this->converter->get_stash('gradebook_nonmodgradeitem', $gradeitemid); + $this->write_xml('grade_item', $gradeitem, array('/grade_item/id')); + } + $this->xmlwriter->end_tag('grade_items'); + } + + /** + * Writes grade_letters + */ + protected function write_grade_letters() { + + $this->xmlwriter->begin_tag('grade_letters'); + foreach ($this->converter->get_stash_itemids('gradebook_gradeletter') as $gradeletterid) { + $gradeletter = $this->converter->get_stash('gradebook_gradeletter', $gradeletterid); + $this->write_xml('grade_letter', $gradeletter, array('/grade_letter/id')); + } + $this->xmlwriter->end_tag('grade_letters'); + } +} + + /** * Shared base class for activity modules, blocks and qtype handlers */ diff --git a/backup/converter/moodle1/lib.php b/backup/converter/moodle1/lib.php index dcee200c6e4..aef888e44c6 100644 --- a/backup/converter/moodle1/lib.php +++ b/backup/converter/moodle1/lib.php @@ -470,6 +470,22 @@ class moodle1_converter extends base_converter { } } + /** + * Restores a given stash or returns the given default if there is no such stash + * + * @param string $stashname name of the stash + * @param int $itemid optional id for multiple infos within the same stashname + * @param mixed $default information to return if the info has not been stashed previously + * @return mixed stashed data or the default value + */ + public function get_stash_or_default($stashname, $itemid = 0, $default = null) { + try { + return $this->get_stash($stashname, $itemid); + } catch (moodle1_convert_empty_storage_exception $e) { + return $default; + } + } + /** * Returns the list of existing stashes * diff --git a/backup/converter/moodle1/simpletest/testlib.php b/backup/converter/moodle1/simpletest/testlib.php index 44634e9f018..addc174d124 100644 --- a/backup/converter/moodle1/simpletest/testlib.php +++ b/backup/converter/moodle1/simpletest/testlib.php @@ -160,6 +160,38 @@ class moodle1_converter_test extends UnitTestCase { $converter->drop_stash_storage(); } + public function test_get_stash_or_default() { + $converter = convert_factory::get_converter('moodle1', $this->tempdir); + $converter->create_stash_storage(); + + $this->assertTrue(is_null($converter->get_stash_or_default('stashname'))); + $this->assertTrue(is_null($converter->get_stash_or_default('stashname', 7))); + $this->assertTrue('default' === $converter->get_stash_or_default('stashname', 0, 'default')); + $this->assertTrue(array('foo', 'bar') === $converter->get_stash_or_default('stashname', 42, array('foo', 'bar'))); + + //$converter->set_stash('stashname', 0); + //$this->assertFalse(is_null($converter->get_stash_or_default('stashname'))); // todo returns true now, this needs MDL-27713 to be fixed + + //$converter->set_stash('stashname', ''); + //$this->assertFalse(is_null($converter->get_stash_or_default('stashname'))); // todo returns true now, this needs MDL-27713 to be fixed + + //$converter->set_stash('stashname', array()); + //$this->assertFalse(is_null($converter->get_stash_or_default('stashname'))); // todo returns true now, this needs MDL-27713 to be fixed + + $converter->set_stash('stashname', 42); + $this->assertTrue(42 === $converter->get_stash_or_default('stashname')); + $this->assertTrue(is_null($converter->get_stash_or_default('stashname', 1))); + $this->assertTrue(42 === $converter->get_stash_or_default('stashname', 0, 61)); + + $converter->set_stash('stashname', array(42 => (object)array('id' => 42)), 18); + $stashed = $converter->get_stash_or_default('stashname', 18, 1984); + $this->assertIsA($stashed, 'array'); + $this->assertTrue(is_object($stashed[42])); + $this->assertTrue($stashed[42]->id === 42); + + $converter->drop_stash_storage(); + } + public function test_get_contextid() { $converter = convert_factory::get_converter('moodle1', $this->tempdir); diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index bab3daa439d..967617d4745 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -146,12 +146,18 @@ class restore_gradebook_structure_step extends restore_structure_step { $data->courseid = $this->get_courseid(); - //manual grade items store category id in categoryid if ($data->itemtype=='manual') { + // manual grade items store category id in categoryid $data->categoryid = $this->get_mappingid('grade_category', $data->categoryid, NULL); - } //course and category grade items store their category id in iteminstance - else if ($data->itemtype=='course' || $data->itemtype=='category') { + } else if ($data->itemtype=='course') { + // course grade item stores their category id in iteminstance + $coursecat = grade_category::fetch_course_category($this->get_courseid()); + $data->iteminstance = $coursecat->id; + } else if ($data->itemtype=='category') { + // category grade items store their category id in iteminstance $data->iteminstance = $this->get_mappingid('grade_category', $data->iteminstance, NULL); + } else { + throw new restore_step_exception('unexpected_grade_item_type', $data->itemtype); } $data->scaleid = $this->get_mappingid('scale', $data->scaleid, NULL); @@ -300,12 +306,16 @@ class restore_gradebook_structure_step extends restore_structure_step { //if this is an activity grade item that needs to be put back in its correct category if (!empty($grade_item_backup->parentitemid)) { - $updateobj->categoryid = $this->get_mappingid('grade_category', $grade_item_backup->parentitemid); + $oldcategoryid = $this->get_mappingid('grade_category', $grade_item_backup->parentitemid, null); + if (!is_null($oldcategoryid)) { + $updateobj->categoryid = $oldcategoryid; + $DB->update_record('grade_items', $updateobj); + } } else { //mark course and category items as needing to be recalculated $updateobj->needsupdate=1; + $DB->update_record('grade_items', $updateobj); } - $DB->update_record('grade_items', $updateobj); } } $rs->close(); diff --git a/backup/util/helper/convert_helper.class.php b/backup/util/helper/convert_helper.class.php index 7301b4bb915..539f4f832dc 100644 --- a/backup/util/helper/convert_helper.class.php +++ b/backup/util/helper/convert_helper.class.php @@ -89,7 +89,7 @@ abstract class convert_helper { $filepath = $dirpath . '/moodle_backup.xml'; if (!is_dir($dirpath)) { - throw new converter_helper_exception('tmp_backup_directory_not_found', $dirpath); + throw new convert_helper_exception('tmp_backup_directory_not_found', $dirpath); } if (!file_exists($filepath)) {