Skip to content

Commit

Permalink
MDL-47787 course: Added question delete to cm delete.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyler Bannister committed Jun 25, 2015
1 parent 38cb73a commit 7f7144f
Show file tree
Hide file tree
Showing 5 changed files with 470 additions and 146 deletions.
103 changes: 103 additions & 0 deletions admin/cli/fix_orphaned_question_categories.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?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/>.

/**
* This script fixes orphaned question categories.
*
* Orphaned question categories have had their associated context deleted
* but the category itself remains in the database with an invalid context.
*
* @package core
* @subpackage cli
* @copyright 2013 Tyler Bannister (tyler.bannister@remote-learner.net)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

define('CLI_SCRIPT', true);

require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/clilib.php');
require_once($CFG->libdir.'/questionlib.php');

$long = array('fix' => false, 'help' => false);
$short = array('f' => 'fix', 'h' => 'help');

// Now get cli options.
list($options, $unrecognized) = cli_get_params($long, $short);

if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}

if ($options['help']) {
$help =
"Fix orphaned question categories.
This scripts detects question categories that have had their
context deleted, thus severing them from their original purpose.
This script will find the orphaned categories and delete the unused
questions in each category found. Used questions will not be
deleted, instead they will be moved to a rescue question category.
Options:
-h, --help Print out this help
-f, --fix Fix the orphaned question categories in the DB.
If not specified only check and report problems to STDERR.
Example:
\$sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_question_categories.php
\$sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_question_categories.php -f
";

echo $help;
die;
}

cli_heading('Checking for orphaned categories');


$sql = 'SELECT qc.id, qc.contextid, qc.name
FROM {question_categories} qc
LEFT JOIN {context} c ON qc.contextid = c.id
WHERE c.id IS NULL';
$categories = $DB->get_recordset_sql($sql);

$i = 0;
foreach ($categories as $category) {
$i += 1;
echo "Found orphaned category: {$category->name}\n";
if (!empty($options['fix'])) {
echo "Cleaning...";
// One transaction per category.
$transaction = $DB->start_delegated_transaction();
question_category_delete_safe($category);
$transaction->allow_commit();
echo " Done!\n";
}
}

if (($i > 0) && !empty($options['fix'])) {
echo "Found and removed {$i} orphaned question categories\n";
} else if ($i > 0) {
echo "Found {$i} orphaned question categories. To fix, run:\n";
echo "\$sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_question_categories.php --fix\n";
} else {
echo "No orphaned question categories found.\n";
}


$categories->close();
8 changes: 6 additions & 2 deletions course/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -1633,7 +1633,7 @@ function set_coursemodule_visible($id, $visible) {
}

/**
* This function will handles the whole deletion process of a module. This includes calling
* This function will handle the whole deletion process of a module. This includes calling
* the modules delete_instance function, deleting files, events, grades, conditional data,
* the data in the course_module and course_sections table and adding a module deletion
* event to the DB.
Expand All @@ -1645,9 +1645,10 @@ function course_delete_module($cmid) {
global $CFG, $DB;

require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/blog/lib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
require_once($CFG->dirroot . '/tag/lib.php');
require_once($CFG->dirroot.'/tag/lib.php');

// Get the course module.
if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
Expand Down Expand Up @@ -1679,6 +1680,9 @@ function course_delete_module($cmid) {
"Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
}

// Delete activity context questions and question categories.
question_delete_activity($cm);

// Call the delete_instance function, if it returns false throw an exception.
if (!$deleteinstancefunction($cm->instance)) {
throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
Expand Down
103 changes: 80 additions & 23 deletions course/tests/courselib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,23 @@ private function update_specific_module_test($modulename) {
return $moduleinfo;
}

/**
* Data provider for course_delete module
*
* @return array An array of arrays contain test data
*/
public function provider_course_delete_module() {
$data = array();

$data['assign'] = array('assign', array('duedate' => time()));
$data['quiz'] = array('quiz', array('duedate' => time()));

return $data;
}

/**
* Test the create_course function
*/
public function test_create_course() {
global $DB;
$this->resetAfterTest(true);
Expand Down Expand Up @@ -1476,51 +1492,92 @@ public function test_moveto_module_in_same_section() {
$this->assertEquals($pagecm->visible, 0);
}

public function test_course_delete_module() {
/**
* Tests the function that deletes a course module
*
* @param string $type The type of module for the test
* @param array $options The options for the module creation
* @dataProvider provider_course_delete_module
*/
public function test_course_delete_module($type, $options) {
global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();

// Create course and modules.
$course = $this->getDataGenerator()->create_course(array('numsections' => 5));
$options['course'] = $course->id;

// Generate an assignment with due date (will generate a course event).
$assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
$module = $this->getDataGenerator()->create_module($type, $options);

// Get the module context.
$modcontext = context_module::instance($assign->cmid);
$modcontext = context_module::instance($module->cmid);

// Verify context exists.
$this->assertInstanceOf('context_module', $modcontext);

// Add some tags to this assignment.
tag_set('assign', $assign->id, array('Tag 1', 'Tag 2', 'Tag 3'), 'mod_assign', $modcontext->id);

// Confirm the tag instances were added.
$this->assertEquals(3, $DB->count_records('tag_instance', array('component' => 'mod_assign', 'contextid' =>
$modcontext->id)));

// Verify event assignment event has been generated.
$eventcount = $DB->count_records('event', array('instance' => $assign->id, 'modulename' => 'assign'));
$this->assertEquals(1, $eventcount);
// Make module specific messes.
switch ($type) {
case 'assign':
// Add some tags to this assignment.
tag_set('assign', $module->id, array('Tag 1', 'Tag 2', 'Tag 3'), 'mod_assign', $modcontext->id);

// Confirm the tag instances were added.
$criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
$this->assertEquals(3, $DB->count_records('tag_instance', $criteria));

// Verify event assignment event has been generated.
$eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
$this->assertEquals(1, $eventcount);

break;
case 'quiz':
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
$qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
$questions = array(
$qgen->create_question('shortanswer', null, array('category' => $qcat->id)),
$qgen->create_question('shortanswer', null, array('category' => $qcat->id)),
);
$this->expectOutputRegex('/'.get_string('unusedcategorydeleted', 'question').'/');
break;
default:
break;
}

// Run delete..
course_delete_module($assign->cmid);
course_delete_module($module->cmid);

// Verify the context has been removed.
$this->assertFalse(context_module::instance($assign->cmid, IGNORE_MISSING));
$this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));

// Verify the course_module record has been deleted.
$cmcount = $DB->count_records('course_modules', array('id' => $assign->cmid));
$cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
$this->assertEmpty($cmcount);

// Verify event assignment events have been removed.
$eventcount = $DB->count_records('event', array('instance' => $assign->id, 'modulename' => 'assign'));
$this->assertEmpty($eventcount);

// Verify the tag instances were deleted.
$this->assertEquals(0, $DB->count_records('tag_instance', array('component' => 'mod_assign', 'contextid' =>
$modcontext->id)));
// Test clean up of module specific messes.
switch ($type) {
case 'assign':
// Verify event assignment events have been removed.
$eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
$this->assertEmpty($eventcount);

// Verify the tag instances were deleted.
$criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
$this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
break;
case 'quiz':
// Verify category deleted.
$criteria = array('contextid' => $modcontext->id);
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));

// Verify questions deleted.
$criteria = array('category' => $qcat->id);
$this->assertEquals(0, $DB->count_records('question', $criteria));
break;
default:
break;
}
}

/**
Expand Down
Loading

0 comments on commit 7f7144f

Please sign in to comment.