MDL-63806 glossary: Move delete code to API function

This commit is contained in:
Juan Leyva
2020-10-09 09:49:59 +02:00
parent 823bc36276
commit 0ab3fd7958
3 changed files with 308 additions and 88 deletions
+4 -88
View File
@@ -46,107 +46,23 @@ if ($cm->instance != $entry->glossaryid) {
require_login($course, false, $cm);
$context = context_module::instance($cm->id);
$manageentries = has_capability('mod/glossary:manageentries', $context);
if (! $glossary = $DB->get_record("glossary", array("id"=>$cm->instance))) {
print_error('invalidid', 'glossary');
}
$strareyousuredelete = get_string("areyousuredelete","glossary");
if (($entry->userid != $USER->id) and !$manageentries) { // guest id is never matched, no need for special check here
print_error('nopermissiontodelentry');
}
$ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
if (!$ineditperiod and !$manageentries) {
print_error('errdeltimeexpired', 'glossary');
}
// Throws an exception if the user cannot delete the entry.
mod_glossary_can_delete_entry($entry, $glossary, $context, false);
/// If data submitted, then process and store.
if ($confirm and confirm_sesskey()) { // the operation was confirmed.
// if it is an imported entry, just delete the relation
$origentry = fullclone($entry);
if ($entry->sourceglossaryid) {
if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) {
print_error('invalidcoursemodule');
}
$newcontext = context_module::instance($newcm->id);
$entry->glossaryid = $entry->sourceglossaryid;
$entry->sourceglossaryid = 0;
$DB->update_record('glossary_entries', $entry);
// move attachments too
$fs = get_file_storage();
if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
foreach ($oldfiles as $oldfile) {
$file_record = new stdClass();
$file_record->contextid = $newcontext->id;
$fs->create_file_from_storedfile($file_record, $oldfile);
}
$fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
$entry->attachment = '1';
} else {
$entry->attachment = '0';
}
$DB->update_record('glossary_entries', $entry);
} else {
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
$DB->delete_records("comments", array('itemid'=>$entry->id, 'commentarea'=>'glossary_entry', 'contextid'=>$context->id));
$DB->delete_records("glossary_alias", array("entryid"=>$entry->id));
$DB->delete_records("glossary_entries", array("id"=>$entry->id));
// Update completion state
$completion = new completion_info($course);
if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
$completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
}
//delete glossary entry ratings
require_once($CFG->dirroot.'/rating/lib.php');
$delopt = new stdClass;
$delopt->contextid = $context->id;
$delopt->component = 'mod_glossary';
$delopt->ratingarea = 'entry';
$delopt->itemid = $entry->id;
$rm = new rating_manager();
$rm->delete_ratings($delopt);
}
// Delete cached RSS feeds.
if (!empty($CFG->enablerssfeeds)) {
require_once($CFG->dirroot.'/mod/glossary/rsslib.php');
glossary_rss_delete_file($glossary);
}
core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id);
$event = \mod_glossary\event\entry_deleted::create(array(
'context' => $context,
'objectid' => $origentry->id,
'other' => array(
'mode' => $prevmode,
'hook' => $hook,
'concept' => $origentry->concept
)
));
$event->add_record_snapshot('glossary_entries', $origentry);
$event->trigger();
// Reset caches.
if ($entry->usedynalink and $entry->approved) {
\mod_glossary\local\concept_cache::reset_glossary($glossary);
}
mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook, $prevmode);
redirect("view.php?id=$cm->id&amp;mode=$prevmode&amp;hook=$hook");
} else { // the operation has not been confirmed yet so ask the user to do so
$strareyousuredelete = get_string("areyousuredelete", "glossary");
$PAGE->navbar->add(get_string('delete'));
$PAGE->set_title($glossary->name);
$PAGE->set_heading($course->fullname);
+137
View File
@@ -4318,3 +4318,140 @@ function mod_glossary_get_completion_active_rule_descriptions($cm) {
}
return $descriptions;
}
/**
* Checks if the current user can delete the given glossary entry.
*
* @since Moodle 3.10
* @param stdClass $entry the entry database object
* @param stdClass $glossary the glossary database object
* @param stdClass $context the glossary context
* @param bool $return Whether to return a boolean value or stop the execution (exception)
* @return bool if the user can delete the entry
* @throws moodle_exception
*/
function mod_glossary_can_delete_entry($entry, $glossary, $context, $return = true) {
global $USER, $CFG;
$manageentries = has_capability('mod/glossary:manageentries', $context);
if ($manageentries) { // Users with the capability will always be able to delete entries.
return true;
}
if ($entry->userid != $USER->id) { // Guest id is never matched, no need for special check here.
if ($return) {
return false;
}
throw new moodle_exception('nopermissiontodelentry');
}
$ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
if (!$ineditperiod) {
if ($return) {
return false;
}
throw new moodle_exception('errdeltimeexpired', 'glossary');
}
return true;
}
/**
* Deletes the given entry, this function does not perform capabilities/permission checks.
*
* @since Moodle 3.10
* @param stdClass $entry the entry database object
* @param stdClass $glossary the glossary database object
* @param stdClass $cm the glossary course moduule object
* @param stdClass $context the glossary context
* @param stdClass $course the glossary course
* @param string $hook the hook, usually type of filtering, value
* @param string $prevmode the previsualisation mode
* @throws moodle_exception
*/
function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook = '', $prevmode = '') {
global $CFG, $DB;
$origentry = fullclone($entry);
// If it is an imported entry, just delete the relation.
if ($entry->sourceglossaryid) {
if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) {
print_error('invalidcoursemodule');
}
$newcontext = context_module::instance($newcm->id);
$entry->glossaryid = $entry->sourceglossaryid;
$entry->sourceglossaryid = 0;
$DB->update_record('glossary_entries', $entry);
// Move attachments too.
$fs = get_file_storage();
if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
foreach ($oldfiles as $oldfile) {
$filerecord = new stdClass();
$filerecord->contextid = $newcontext->id;
$fs->create_file_from_storedfile($filerecord, $oldfile);
}
$fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
$entry->attachment = '1';
} else {
$entry->attachment = '0';
}
$DB->update_record('glossary_entries', $entry);
} else {
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
$DB->delete_records("comments",
['itemid' => $entry->id, 'commentarea' => 'glossary_entry', 'contextid' => $context->id]);
$DB->delete_records("glossary_alias", ["entryid" => $entry->id]);
$DB->delete_records("glossary_entries", ["id" => $entry->id]);
// Update completion state.
$completion = new completion_info($course);
if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
$completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
}
// Delete glossary entry ratings.
require_once($CFG->dirroot.'/rating/lib.php');
$delopt = new stdClass;
$delopt->contextid = $context->id;
$delopt->component = 'mod_glossary';
$delopt->ratingarea = 'entry';
$delopt->itemid = $entry->id;
$rm = new rating_manager();
$rm->delete_ratings($delopt);
}
// Delete cached RSS feeds.
if (!empty($CFG->enablerssfeeds)) {
require_once($CFG->dirroot . '/mod/glossary/rsslib.php');
glossary_rss_delete_file($glossary);
}
core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id);
$event = \mod_glossary\event\entry_deleted::create(
[
'context' => $context,
'objectid' => $origentry->id,
'other' => [
'mode' => $prevmode,
'hook' => $hook,
'concept' => $origentry->concept
]
]
);
$event->add_record_snapshot('glossary_entries', $origentry);
$event->trigger();
// Reset caches.
if ($entry->usedynalink and $entry->approved) {
\mod_glossary\local\concept_cache::reset_glossary($glossary);
}
}
+167
View File
@@ -503,4 +503,171 @@ class mod_glossary_lib_testcase extends advanced_testcase {
$search = glossary_get_entries_search($concept, $course->id);
$this->assertCount(0, $search);
}
public function test_mod_glossary_can_delete_entry_users() {
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
$context = context_module::instance($glossary->cmid);
// Test student can delete.
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test teacher can delete.
$this->setUser($teacher);
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test admin can delete.
$this->setAdminUser();
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test a different student is not able to delete.
$this->setUser($anotherstudent);
$this->assertFalse(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test exception.
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
mod_glossary_can_delete_entry($entry, $glossary, $context, false);
}
public function test_mod_glossary_can_delete_entry_edit_period() {
global $CFG;
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id, 'editalways' => 1]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
$context = context_module::instance($glossary->cmid);
// Test student can always delete when edit always is set to 1.
$entry->timecreated = time() - 2 * $CFG->maxeditingtime;
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test student cannot delete old entries when edit always is set to 0.
$glossary->editalways = 0;
$this->assertFalse(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test student can delete recent entries when edit always is set to 0.
$entry->timecreated = time();
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Check exception.
$entry->timecreated = time() - 2 * $CFG->maxeditingtime;
$this->expectExceptionMessage(get_string('errdeltimeexpired', 'glossary'));
mod_glossary_can_delete_entry($entry, $glossary, $context, false);
}
public function test_mod_glossary_delete_entry() {
global $DB, $CFG;
$this->resetAfterTest();
require_once($CFG->dirroot . '/rating/lib.php');
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$record = new stdClass();
$record->course = $course->id;
$record->assessed = RATING_AGGREGATE_AVERAGE;
$scale = $this->getDataGenerator()->create_scale(['scale' => 'A,B,C,D']);
$record->scale = "-$scale->id";
$glossary = $this->getDataGenerator()->create_module('glossary', $record);
$context = context_module::instance($glossary->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student1);
// Create entry with tags and rating.
$entry = $gg->create_content(
$glossary,
['approved' => 1, 'userid' => $student1->id, 'tags' => ['Cats', 'Dogs']],
['alias1', 'alias2']
);
// Rate the entry as user2.
$rating1 = new stdClass();
$rating1->contextid = $context->id;
$rating1->component = 'mod_glossary';
$rating1->ratingarea = 'entry';
$rating1->itemid = $entry->id;
$rating1->rating = 1; // 1 is A.
$rating1->scaleid = "-$scale->id";
$rating1->userid = $student2->id;
$rating1->timecreated = time();
$rating1->timemodified = time();
$rating1->id = $DB->insert_record('rating', $rating1);
$sink = $this->redirectEvents();
mod_glossary_delete_entry(fullclone($entry), $glossary, $cm, $context, $course);
$events = $sink->get_events();
$event = array_pop($events);
// Check events.
$this->assertEquals('\mod_glossary\event\entry_deleted', $event->eventname);
$this->assertEquals($entry->id, $event->objectid);
$sink->close();
// No entry, no alias, no ratings, no tags.
$this->assertEquals(0, $DB->count_records('glossary_entries', ['id' => $entry->id]));
$this->assertEquals(0, $DB->count_records('glossary_alias', ['entryid' => $entry->id]));
$this->assertEquals(0, $DB->count_records('rating', ['component' => 'mod_glossary', 'itemid' => $entry->id]));
$this->assertEmpty(core_tag_tag::get_by_name(0, 'Cats'));
}
public function test_mod_glossary_delete_entry_imported() {
global $DB;
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$glossary1 = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$glossary2 = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$context = context_module::instance($glossary2->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary2->id);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry1 = $gg->create_content($glossary1);
$entry2 = $gg->create_content(
$glossary2,
['approved' => 1, 'userid' => $student->id, 'sourceglossaryid' => $glossary1->id, 'tags' => ['Cats', 'Dogs']]
);
$sink = $this->redirectEvents();
mod_glossary_delete_entry(fullclone($entry2), $glossary2, $cm, $context, $course);
$events = $sink->get_events();
$event = array_pop($events);
// Check events.
$this->assertEquals('\mod_glossary\event\entry_deleted', $event->eventname);
$this->assertEquals($entry2->id, $event->objectid);
$sink->close();
// Check source.
$this->assertEquals(0, $DB->get_field('glossary_entries', 'sourceglossaryid', ['id' => $entry2->id]));
$this->assertEquals($glossary1->id, $DB->get_field('glossary_entries', 'glossaryid', ['id' => $entry2->id]));
// Tags.
$this->assertEmpty(core_tag_tag::get_by_name(0, 'Cats'));
}
}