From dde8a5b62c432b78c2fdd3e892475b93d85e17ef Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Wed, 6 Jan 2021 12:02:06 +0200 Subject: [PATCH] MDL-66769 core_h5p: Clean up orphaned h5p records task. --- lang/en/admin.php | 1 + .../task/h5p_clean_orphaned_records_task.php | 74 ++++++++++++++++ lib/db/tasks.php | 9 ++ .../h5p_clean_orphaned_records_task_test.php | 85 +++++++++++++++++++ version.php | 2 +- 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 lib/classes/task/h5p_clean_orphaned_records_task.php create mode 100644 lib/tests/h5p_clean_orphaned_records_task_test.php diff --git a/lang/en/admin.php b/lang/en/admin.php index 65fa702420a..6de019dca65 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -688,6 +688,7 @@ $string['checkboxno'] = 'No'; $string['checkboxyes'] = 'Yes'; $string['choosefiletoedit'] = 'Choose file to edit'; $string['h5pgetcontenttypestask'] = 'Download available H5P content types from h5p.org'; +$string['taskh5pcleanup'] = 'Unused H5P files cleanup'; $string['iconvrequired'] = 'Installing ICONV extension is required.'; $string['ignore'] = 'Ignore'; $string['includemoduleuserdata'] = 'Include module user data'; diff --git a/lib/classes/task/h5p_clean_orphaned_records_task.php b/lib/classes/task/h5p_clean_orphaned_records_task.php new file mode 100644 index 00000000000..37fc6056527 --- /dev/null +++ b/lib/classes/task/h5p_clean_orphaned_records_task.php @@ -0,0 +1,74 @@ +. + +namespace core\task; + +/** + * A schedule task to clean orphaned h5p records (for example for deleted activity). + * + * @package core_h5p + * @copyright 2021 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class h5p_clean_orphaned_records_task extends scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('taskh5pcleanup', 'admin'); + } + + /** + * Execute the task. + */ + public function execute() { + global $DB; + + $sql = "SELECT h5p.id + FROM {h5p} h5p + LEFT JOIN {files} f + ON f.pathnamehash = h5p.pathnamehash + WHERE f.pathnamehash IS NULL"; + + $orphanedrecords = $DB->get_recordset_sql($sql); + + foreach ($orphanedrecords as $orphanedrecord) { + + $sql = "SELECT f.id, f.pathnamehash + FROM {files} f + WHERE f.itemid = :itemid + AND f.filearea = :filearea + AND f.component = :component"; + $params = ['itemid' => $orphanedrecord->id, 'filearea' => 'content', 'component' => 'core_h5p']; + $filerecords = $DB->get_recordset_sql($sql, $params); + + foreach ($filerecords as $filerecord) { + $fs = get_file_storage(); + $file = $fs->get_file_by_hash($filerecord->pathnamehash); + if ($file) { + $file->delete(); + } + } + + $DB->delete_records('h5p', ['id' => $orphanedrecord->id]); + $DB->delete_records('h5p_contents_libraries', ['h5pid' => $orphanedrecord->id]); + } + + } +} diff --git a/lib/db/tasks.php b/lib/db/tasks.php index 9c473ea654b..9393cfca996 100644 --- a/lib/db/tasks.php +++ b/lib/db/tasks.php @@ -401,6 +401,15 @@ $tasks = array( 'dayofweek' => '*', 'month' => '*' ), + array( + 'classname' => 'core\task\h5p_clean_orphaned_records_task', + 'blocking' => 0, + 'minute' => 'R', + 'hour' => '0', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*' + ), array( 'classname' => 'core\task\antivirus_cleanup_task', 'blocking' => 0, diff --git a/lib/tests/h5p_clean_orphaned_records_task_test.php b/lib/tests/h5p_clean_orphaned_records_task_test.php new file mode 100644 index 00000000000..561c2516c94 --- /dev/null +++ b/lib/tests/h5p_clean_orphaned_records_task_test.php @@ -0,0 +1,85 @@ +. + +/** + * Class containing unit tests for the task to clean orphaned h5p records. + * + * @package core + * @copyright 2021 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + */ +class h5p_clean_orphaned_records_task_test extends advanced_testcase { + + /** + * Test task execution + * + * return void + */ + public function test_task_execution(): void { + global $CFG, $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + $params = [ + 'course' => $course->id, + 'packagefilepath' => $CFG->dirroot.'/h5p/tests/fixtures/greeting-card-887.h5p', + 'introformat' => 1 + ]; + + // Create h5pactivity. + $activity = $this->getDataGenerator()->create_module('h5pactivity', $params); + $activity->filename = 'greeting-card-887.h5p'; + $context = context_module::instance($activity->cmid); + + // Create a fake deploy H5P file. + $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); + $generator->create_export_file($activity->filename, $context->id, + 'mod_h5pactivity', 'package'); + + // Delete activity. + course_delete_module($activity->cmid); + + $orphanedh5psql = "SELECT h5p.id, h5p.pathnamehash + FROM {h5p} h5p + LEFT JOIN {files} f ON f.pathnamehash = h5p.pathnamehash + WHERE f.pathnamehash IS NULL"; + $orphanedh5p = $DB->get_records_sql($orphanedh5psql); + $this->assertEquals(1, count($orphanedh5p)); + + $h5pid = end($orphanedh5p)->id; + $orphanedfilessql = "SELECT id + FROM {files} + WHERE itemid = :h5pid + AND filearea = 'content' + AND component = 'core_h5p'"; + $orphanedfiles = $DB->get_records_sql($orphanedfilessql, ['h5pid' => $h5pid]); + $this->assertEquals(3, count($orphanedfiles)); + + // Execute task. + $task = new \core\task\h5p_clean_orphaned_records_task(); + $task->execute(); + + $orphanedh5p = $DB->get_record_sql($orphanedh5psql); + $this->assertFalse($orphanedh5p); + + $orphanedfiles = $DB->get_record_sql($orphanedfilessql, ['h5pid' => $h5pid]); + $this->assertFalse($orphanedfiles); + } +} diff --git a/version.php b/version.php index 468878bdb2a..1fe0747f146 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021052500.54; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021052500.55; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev (Build: 20210130)'; // Human-friendly version name