diff --git a/classes/task/lti13_migration_task.php b/classes/task/lti13_migration_task.php new file mode 100644 index 0000000..0ab7d45 --- /dev/null +++ b/classes/task/lti13_migration_task.php @@ -0,0 +1,132 @@ +. + +namespace mod_equella\task; + +use core\task\adhoc_task; +use dml_exception; +use stdClass; + +global $CFG; +require_once($CFG->dirroot . '/mod/lti/lib.php'); +require_once($CFG->dirroot . '/mod/lti/locallib.php'); + +class lti13_migration_task extends adhoc_task +{ + /** + * Create a new LTI external tool instance based on the provided course module info, details of the OEQ instance used in this + * course module and the configurations of an LTI external tool. + * + * @param $courseModule stdClass An instance of CourseModule to be updated to use LTI 1.3 as its module. + * @param $ltiToolDetails stdClass Details of the LTI external tool to be used to generate a new LTI instance. + * + * @throws dml_exception + */ + private function createLtiInstance($courseModule, $ltiToolDetails): int + { + global $DB; + + $oeqInstance = $DB->get_record('equella', array('id' => $courseModule->instance)); + $ltiConfigurations = $ltiToolDetails->configurations; + $ltiToolID = $ltiToolDetails->id; + + $lti = new stdClass(); + + $lti->typeid = $ltiToolID; + + $lti->course = $courseModule->course; + $lti->showdescriptionlaunch = $courseModule->showdescription; + $lti->coursemodule = $courseModule->id; + + $lti->icon = unserialize($oeqInstance->metadata)['thumbnail']; + $lti->intro = $oeqInstance->intro; + $lti->introformat = $oeqInstance->introformat; + $lti->name = $oeqInstance->name; + $lti->timecreated = $oeqInstance->timecreated; + $lti->timemodified = $oeqInstance->timemodified; + $lti->toolurl = $oeqInstance->url; + + $lti->instructorchoicesendname = $ltiConfigurations['sendname'] ?? 1; + $lti->instructorchoicesendmailaddr = $ltiConfigurations['sendemailaddr'] ?? 1; + $lti->instructorchoiceacceptgrades = $ltiConfigurations['acceptgrades'] == LTI_SETTING_ALWAYS ? $ltiConfigurations['acceptgrades'] : 0; + $lti->instructorchoiceallowsetting = $ltiConfigurations['ltiservice_toolsettings'] ?? null; + $lti->instructorchoiceallowroster = $ltiConfigurations['allowroster ltiservice_memberships'] ?? null; + $lti->instructorcustomparameters = $ltiConfigurations['customparameters'] ?? ""; + $lti->launchcontainer = $ltiConfigurations['launchcontainer']; + + return lti_add_instance($lti, null); // The second parameter is not used at all in this function.; + } + + /** + * Update an existing course module to use a new LTI instance. To achieve this, the value of `module` needs to be + * updated to the ID of LTI module, and the value of instance needs to be updated to the ID of a new LTI instance. + * + * @param $courseModule stdClass An instance of CourseModule to be updated to use LTI 1.3 as its module. + * @param $ltiToolDetails stdClass Details of the LTI external tool to be used to generate a new LTI instance. + * @param $ltiModuleID int ID of the LTI module. + * + * @throws dml_exception + */ + private function updateCourseModule($courseModule, $ltiToolDetails, $ltiModuleID): void + { + global $DB; + + $courseModule->module = $ltiModuleID; + $courseModule->instance = $this->createLtiInstance($courseModule, $ltiToolDetails); + $DB->update_record("course_modules", $courseModule); + } + + /** + * Return the ID of Moodle module for the provided module name. + * + * @param $moduleName string Name of a Moodle module. + * + * @throws dml_exception + */ + private function getModuleId($moduleName) { + global $DB; + return $DB->get_field_sql("SELECT id FROM {modules} WHERE name = '$moduleName'"); + } + + public function execute() + { + global $DB; + + try { + $oeqMoodleModuleID = $this->getModuleId('equella'); + $ltiModuleID = $this->getModuleId('lti'); + + $ltiTool = new stdClass(); + $ltiTypeName = $this->get_custom_data(); + $ltiTypeID = $DB->get_field_sql("SELECT id FROM {lti_types} WHERE name = '" . $ltiTypeName . "'"); + $ltiTool->id = $ltiTypeID; + $ltiTool->configurations = lti_get_type_config($ltiTypeID); + + $courseModuleList = $DB->get_records_sql("SELECT * FROM {course_modules} cm WHERE cm.module = " . $oeqMoodleModuleID); + + foreach ($courseModuleList as $cm) { + echo "Processing course ID: " . $cm->course . " openEQUELLA resource ID: " . $cm->instance . "\n"; + $this->updateCourseModule($cm, $ltiTool, $ltiModuleID); + } + + echo "LTI 1.3 migration has been successfully completed!"; + } catch (dml_exception $e) { + echo "LTI 1.3 migration failed: $e"; + } + } + + +} \ No newline at end of file diff --git a/lang/en/equella.php b/lang/en/equella.php index 02dc7fa..385e7c8 100644 --- a/lang/en/equella.php +++ b/lang/en/equella.php @@ -101,6 +101,11 @@ $string['config.lti.secret.help'] = 'Client secret is required if LTI is enabled.'; $string['config.lti.liscallback.title'] = 'Outcome callback URL'; +//////////////////////////////////////////////////////// +// LTI 1.3 migration +$string['config.lti13.migration.title'] = 'LTI 1.3 Migration'; +$string['config.lti13.migration.description'] = 'Migrate openEQUELLA resources to use LTI 1.3'; + //////////////////////////////////////////////////////// // CONFIGURATION: Shared Secrets diff --git a/lti13migration/downloadcsv.php b/lti13migration/downloadcsv.php new file mode 100644 index 0000000..184051b --- /dev/null +++ b/lti13migration/downloadcsv.php @@ -0,0 +1,61 @@ +. + +require_once('../../../config.php'); +require_once($CFG->dirroot . '/mod/lti/classes/external.php'); +require_once($CFG->dirroot . '/lib/datalib.php'); +require_once($CFG->dirroot . '/mod/equella/lib.php'); + +global $DB; + +header('Content-Type: text/csv; charset=utf-8'); +header('Content-Disposition: attachment; filename=courses.csv'); + +// Cannot use `$DB0->get_field` due to issue . +$oeqMoodleModuleID = $DB->get_field_sql("SELECT id FROM {modules} WHERE name = 'equella'"); + +// Moodle does not provide a function that can return a list of courses that contain certain specified modules. +// One would have to get a full list of courses and then filter the list by checking whether each course has +// the specified modules. +// So using the below SQL should be better. +$courseSql = "SELECT c.id, c.fullname FROM {course} c, {course_modules} cm" . + " WHERE cm.module = " . $oeqMoodleModuleID . + " AND c.id = cm.course" . + " GROUP BY c.id, c.fullname HAVING count(*) > 0"; +$courses = $DB->get_records_sql($courseSql); + +$csvHeaders = array('Course ID', 'Course name', 'Course URL'); +$csvContent = array_map(function ($course) { + $courseId = $course->id; + $courseUrl = new moodle_url('/course/view.php', array('id' => $courseId)); + return array($courseId, $course->fullname, $courseUrl->out(false)); +}, $courses); + +ob_start(); + +$outputBuffer = fopen('php://output', 'w'); +fputcsv($outputBuffer, $csvHeaders); + +foreach ($csvContent as $i => $row) { + fputcsv($outputBuffer, $row); + if ($i % 100 == 0) { + ob_flush(); + flush(); + } +} + +fclose($outputBuffer); +exit; diff --git a/lti13migration/main.php b/lti13migration/main.php new file mode 100644 index 0000000..5b29a16 --- /dev/null +++ b/lti13migration/main.php @@ -0,0 +1,56 @@ + + + + +LTI 1.3 Migration + + + +get_fieldset_sql("SELECT name FROM {lti_types} WHERE ltiversion='1.3.0'"); +$options = implode(array_map(function ($name) { return ""; }, $ltiToolNames)); + +echo " +

Update oEQ resource links to use LTI 1.3

+ +

+ Before starting the migration, you can download a CSV which will list all courses which contain items that will be + affected by the migration. This CSV can be used for review, and no system modifications will occur. +

+
+ +
+ +

+ This migration will enable existing openEQUELLA links to be opened using LTI 1.3 technology. openEQUELLA items shown + in courses may display slightly differently (e.g. use different thumbnails) after the migration. Please ensure that + you have backed up your Moodle data before proceeding. +

+
+ + + + +
" +?> + + + diff --git a/lti13migration/migrate.php b/lti13migration/migrate.php new file mode 100644 index 0000000..3801c16 --- /dev/null +++ b/lti13migration/migrate.php @@ -0,0 +1,47 @@ + out(false); +} + +$runningTasksPage = buildUrl('/admin/tool/task/runningtasks.php'); +$taskLogsPage = buildUrl('/admin/tasklogs.php'); +$purgeCachePage = buildUrl('/admin/purgecaches.php'); + +$task = new \mod_equella\task\lti13_migration_task(); +$task -> set_custom_data($_GET['ltiTypeName']); +\core\task\manager::queue_adhoc_task($task); +?> + + + +LTI 1.3 Migration + + +

Update oEQ resource links to use LTI 1.3

+ +

+ A Moodle adhoc task has been submitted to do the migration. +

+

+ However, when the task will start depends on how often the Moodle cron script is executed. +

+

+ Running tasks page, + and you can find out the task result in the Task logs page."; + ?> +

+

+ Purge caches page."; + ?> +

+ + + diff --git a/settings.php b/settings.php index 0069293..946fe66 100644 --- a/settings.php +++ b/settings.php @@ -105,4 +105,12 @@ function ecs($configoption, $params = null) { $intercepttype = new admin_setting_configselect('equella_intercept_files', get_string('interceptfiles', 'equella'), get_string('interceptfilesintro', 'equella'), 0, $choices); $settings->add($intercepttype); + + // /////////////////////////////////////////////////////////////////////////////// + // + // LTI 1.3 migration + // + $settings->add(new admin_setting_heading('equella_lti_migration', ecs('lti13.migration.title'), '')); + $lti13MigrationUrl = new moodle_url('/mod/equella/lti13migration/main.php'); + $settings->add(new admin_setting_openlink('lti13migration', ecs('lti13.migration.title'), ecs('lti13.migration.description'), $lti13MigrationUrl->out())); }