mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-07 20:58:35 -05:00
a
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Hook listener for filter_mediacms navigation hooks (Moodle 4.3+)
|
||||
*
|
||||
* @package filter_mediacms
|
||||
* @copyright 2026 MediaCMS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_mediacms;
|
||||
|
||||
/**
|
||||
* Extends the primary (top) navigation bar with a My Media link.
|
||||
*/
|
||||
class hook_listener {
|
||||
|
||||
/**
|
||||
* Called by the \core\hook\navigation\primary_extend hook.
|
||||
* Adds the My Media link to the primary nav bar when placement = 'top'.
|
||||
*/
|
||||
public static function extend_primary_navigation(
|
||||
\core\hook\navigation\primary_extend $hook
|
||||
): void {
|
||||
$placement = get_config('filter_mediacms', 'mymedia_placement');
|
||||
if ($placement !== 'top') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isloggedin() || isguestuser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = new \moodle_url('/filter/mediacms/my_media.php');
|
||||
$node = \navigation_node::create(
|
||||
get_string('mymedia', 'filter_mediacms'),
|
||||
$url,
|
||||
\navigation_node::TYPE_CUSTOM,
|
||||
null,
|
||||
'mediacms_mymedia',
|
||||
new \pix_icon('i/media', '')
|
||||
);
|
||||
|
||||
$primarynav = $hook->get_primarynav();
|
||||
if ($primarynav === null) {
|
||||
return;
|
||||
}
|
||||
$primarynav->add_node($node);
|
||||
}
|
||||
}
|
||||
13
lms-plugins/mediacms-moodle/filter/mediacms/db/hooks.php
Normal file
13
lms-plugins/mediacms-moodle/filter/mediacms/db/hooks.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Hook registrations for filter_mediacms.
|
||||
* Primary navigation is handled via extend_navigation() in lib.php instead.
|
||||
*
|
||||
* @package filter_mediacms
|
||||
* @copyright 2026 MediaCMS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$callbacks = [];
|
||||
@@ -17,3 +17,13 @@ $string['iframeheight_desc'] = 'Default height for embedded videos (pixels).';
|
||||
$string['enableautoconvert'] = 'Auto-convert URLs';
|
||||
$string['enableautoconvert_desc'] = 'Automatically convert MediaCMS URLs (e.g., /view?m=xyz) in text to embedded players.';
|
||||
$string['privacy:metadata'] = 'The MediaCMS filter does not store any personal data.';
|
||||
|
||||
// My Media page.
|
||||
$string['mymedia'] = 'My Media';
|
||||
$string['mymedia_placement'] = 'My Media link placement';
|
||||
$string['mymedia_placement_desc'] = 'Where to display the My Media link in the Moodle interface.';
|
||||
$string['mymedia_placement_top'] = 'Top navigation';
|
||||
$string['mymedia_placement_user'] = 'User navigation';
|
||||
$string['notconfigured'] = 'MediaCMS is not fully configured. Please set the MediaCMS URL and LTI Tool in Site Administration → Plugins → Filters → MediaCMS.';
|
||||
$string['ltitoolnotfound'] = 'The configured LTI tool could not be found. Please check the MediaCMS filter settings.';
|
||||
$string['cannotcreatedummyactivity'] = 'Could not create the MediaCMS launcher activity. Please check course permissions.';
|
||||
|
||||
@@ -11,65 +11,10 @@ require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
require_once($CFG->dirroot . '/course/modlib.php');
|
||||
require_once(__DIR__ . '/locallib.php');
|
||||
|
||||
global $SITE, $DB, $PAGE, $OUTPUT, $CFG;
|
||||
|
||||
/**
|
||||
* Find first LTI activity for the MediaCMS tool, or create dummy if none exists
|
||||
*/
|
||||
function filter_mediacms_get_dummy_activity($courseid, $typeid) {
|
||||
global $DB;
|
||||
|
||||
// Find any existing LTI activity with this tool
|
||||
$sql = "SELECT cm.id
|
||||
FROM {course_modules} cm
|
||||
JOIN {modules} m ON m.id = cm.module
|
||||
JOIN {lti} lti ON lti.id = cm.instance
|
||||
WHERE cm.course = :courseid
|
||||
AND m.name = 'lti'
|
||||
AND lti.typeid = :typeid
|
||||
AND cm.deletioninprogress = 0
|
||||
LIMIT 1";
|
||||
|
||||
$existing = $DB->get_record_sql($sql, ['courseid' => $courseid, 'typeid' => $typeid]);
|
||||
if ($existing) {
|
||||
// Ensure it's accessible (fix if created with visible=0)
|
||||
$cm = get_coursemodule_from_id('lti', $existing->id, 0, false, IGNORE_MISSING);
|
||||
if ($cm && !$cm->visible) {
|
||||
set_coursemodule_visible($existing->id, 1);
|
||||
}
|
||||
return $existing->id;
|
||||
}
|
||||
|
||||
// No existing activity - create dummy in stealth mode (accessible but completely hidden)
|
||||
$moduleinfo = new stdClass();
|
||||
$moduleinfo->course = $courseid;
|
||||
$moduleinfo->module = $DB->get_field('modules', 'id', ['name' => 'lti']);
|
||||
$moduleinfo->modulename = 'lti';
|
||||
$moduleinfo->section = 0;
|
||||
$moduleinfo->visible = 1; // Accessible to all
|
||||
$moduleinfo->visibleoncoursepage = 0; // Hidden from course page
|
||||
$moduleinfo->availability = null; // No restrictions
|
||||
$moduleinfo->showdescription = 0; // Don't show description
|
||||
$moduleinfo->name = 'MediaCMS Filter Launcher';
|
||||
$moduleinfo->intro = ''; // Empty intro
|
||||
$moduleinfo->introformat = FORMAT_HTML;
|
||||
$moduleinfo->typeid = $typeid;
|
||||
$moduleinfo->instructorchoiceacceptgrades = 0;
|
||||
$moduleinfo->grade = 0;
|
||||
$moduleinfo->instructorchoicesendname = 1;
|
||||
$moduleinfo->instructorchoicesendemailaddr = 1;
|
||||
$moduleinfo->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
|
||||
$moduleinfo->instructorcustomparameters = '';
|
||||
|
||||
$result = add_moduleinfo($moduleinfo, get_course($courseid));
|
||||
|
||||
// Additionally hide from activity navigation by setting it as orphaned
|
||||
set_coursemodule_visible($result->coursemodule, 1, 0); // visible but not on course page
|
||||
|
||||
return $result->coursemodule;
|
||||
}
|
||||
|
||||
require_login();
|
||||
|
||||
$mediatoken = required_param('token', PARAM_ALPHANUMEXT);
|
||||
|
||||
72
lms-plugins/mediacms-moodle/filter/mediacms/lib.php
Normal file
72
lms-plugins/mediacms-moodle/filter/mediacms/lib.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Navigation callbacks for filter_mediacms
|
||||
*
|
||||
* @package filter_mediacms
|
||||
* @copyright 2026 MediaCMS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Add My Media to the global / flat navigation (nav drawer).
|
||||
* Fires on every page when placement is set to 'top'.
|
||||
* In Moodle 4.x Boost the nav drawer is opened via the hamburger icon
|
||||
* and the node appears as a top-level item alongside Home / My courses.
|
||||
*/
|
||||
function filter_mediacms_extend_navigation(global_navigation $navigation): void {
|
||||
$placement = get_config('filter_mediacms', 'mymedia_placement');
|
||||
if ($placement !== 'top') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isloggedin() || isguestuser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = new moodle_url('/filter/mediacms/my_media.php');
|
||||
$node = navigation_node::create(
|
||||
get_string('mymedia', 'filter_mediacms'),
|
||||
$url,
|
||||
navigation_node::TYPE_CUSTOM,
|
||||
null,
|
||||
'mediacms_mymedia',
|
||||
new pix_icon('i/media', '')
|
||||
);
|
||||
// showinflatnavigation = true makes it visible in the Boost nav drawer.
|
||||
$node->showinflatnavigation = true;
|
||||
$navigation->add_node($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add My Media to the user account / settings navigation.
|
||||
* Fires when placement is set to 'user'.
|
||||
* In Moodle 4.x Boost this section is reachable via avatar → Preferences.
|
||||
*/
|
||||
function filter_mediacms_extend_navigation_user_settings(
|
||||
navigation_node $navigation,
|
||||
stdClass $user,
|
||||
context_user $usercontext,
|
||||
stdClass $course,
|
||||
context_course $coursecontext
|
||||
): void {
|
||||
$placement = get_config('filter_mediacms', 'mymedia_placement');
|
||||
if ($placement !== 'user') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isloggedin() || isguestuser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = new moodle_url('/filter/mediacms/my_media.php');
|
||||
$navigation->add(
|
||||
get_string('mymedia', 'filter_mediacms'),
|
||||
$url,
|
||||
navigation_node::TYPE_SETTING,
|
||||
null,
|
||||
'mediacms_mymedia',
|
||||
new pix_icon('i/media', '')
|
||||
);
|
||||
}
|
||||
67
lms-plugins/mediacms-moodle/filter/mediacms/locallib.php
Normal file
67
lms-plugins/mediacms-moodle/filter/mediacms/locallib.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Local helper functions for filter_mediacms
|
||||
*
|
||||
* @package filter_mediacms
|
||||
* @copyright 2026 MediaCMS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Find the first LTI activity for the MediaCMS tool in a course, or create a
|
||||
* hidden dummy one if none exists.
|
||||
*
|
||||
* @param int $courseid
|
||||
* @param int $typeid LTI tool type ID
|
||||
* @return int course-module ID
|
||||
*/
|
||||
function filter_mediacms_get_dummy_activity($courseid, $typeid) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT cm.id
|
||||
FROM {course_modules} cm
|
||||
JOIN {modules} m ON m.id = cm.module
|
||||
JOIN {lti} lti ON lti.id = cm.instance
|
||||
WHERE cm.course = :courseid
|
||||
AND m.name = 'lti'
|
||||
AND lti.typeid = :typeid
|
||||
AND cm.deletioninprogress = 0
|
||||
LIMIT 1";
|
||||
|
||||
$existing = $DB->get_record_sql($sql, ['courseid' => $courseid, 'typeid' => $typeid]);
|
||||
if ($existing) {
|
||||
$cm = get_coursemodule_from_id('lti', $existing->id, 0, false, IGNORE_MISSING);
|
||||
if ($cm && !$cm->visible) {
|
||||
set_coursemodule_visible($existing->id, 1);
|
||||
}
|
||||
return $existing->id;
|
||||
}
|
||||
|
||||
// Create a stealth dummy activity (accessible but hidden from the course page).
|
||||
$moduleinfo = new stdClass();
|
||||
$moduleinfo->course = $courseid;
|
||||
$moduleinfo->module = $DB->get_field('modules', 'id', ['name' => 'lti']);
|
||||
$moduleinfo->modulename = 'lti';
|
||||
$moduleinfo->section = 0;
|
||||
$moduleinfo->visible = 1;
|
||||
$moduleinfo->visibleoncoursepage = 0;
|
||||
$moduleinfo->availability = null;
|
||||
$moduleinfo->showdescription = 0;
|
||||
$moduleinfo->name = 'MediaCMS Filter Launcher';
|
||||
$moduleinfo->intro = '';
|
||||
$moduleinfo->introformat = FORMAT_HTML;
|
||||
$moduleinfo->typeid = $typeid;
|
||||
$moduleinfo->instructorchoiceacceptgrades = 0;
|
||||
$moduleinfo->grade = 0;
|
||||
$moduleinfo->instructorchoicesendname = 1;
|
||||
$moduleinfo->instructorchoicesendemailaddr = 1;
|
||||
$moduleinfo->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
|
||||
$moduleinfo->instructorcustomparameters = '';
|
||||
|
||||
$result = add_moduleinfo($moduleinfo, get_course($courseid));
|
||||
set_coursemodule_visible($result->coursemodule, 1, 0);
|
||||
|
||||
return $result->coursemodule;
|
||||
}
|
||||
78
lms-plugins/mediacms-moodle/filter/mediacms/lti_launch.php
Normal file
78
lms-plugins/mediacms-moodle/filter/mediacms/lti_launch.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* My Media LTI launch page — runs inside the iframe from my_media.php.
|
||||
*
|
||||
* Builds custom_publishdata (enrolled courses + roles) and initiates
|
||||
* the LTI 1.3 OIDC login flow, outputting the auto-submit form directly.
|
||||
*
|
||||
* @package filter_mediacms
|
||||
* @copyright 2026 MediaCMS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
require_once($CFG->dirroot . '/course/modlib.php');
|
||||
require_once(__DIR__ . '/locallib.php');
|
||||
|
||||
global $SITE, $DB, $CFG, $USER;
|
||||
|
||||
require_login();
|
||||
|
||||
$mediacmsurl = get_config('filter_mediacms', 'mediacmsurl');
|
||||
$ltitoolid = get_config('filter_mediacms', 'ltitoolid');
|
||||
|
||||
if (empty($mediacmsurl) || empty($ltitoolid)) {
|
||||
throw new moodle_exception('notconfigured', 'filter_mediacms');
|
||||
}
|
||||
|
||||
$type = $DB->get_record('lti_types', ['id' => $ltitoolid]);
|
||||
if (!$type) {
|
||||
throw new moodle_exception('ltitoolnotfound', 'filter_mediacms');
|
||||
}
|
||||
|
||||
// Build custom_publishdata: all courses the user is enrolled in + role.
|
||||
$enrolled_courses = enrol_get_users_courses($USER->id, true, ['id', 'shortname', 'fullname']);
|
||||
|
||||
$publish_data = [];
|
||||
foreach ($enrolled_courses as $enrolled_course) {
|
||||
if ((int)$enrolled_course->id === SITEID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$course_context = context_course::instance($enrolled_course->id);
|
||||
$roles = get_user_roles($course_context, $USER->id, false);
|
||||
|
||||
$role_shortname = 'student';
|
||||
if (!empty($roles)) {
|
||||
$role = reset($roles);
|
||||
$role_shortname = $role->shortname;
|
||||
}
|
||||
|
||||
$publish_data[] = [
|
||||
'id' => (int)$enrolled_course->id,
|
||||
'shortname' => $enrolled_course->shortname,
|
||||
'fullname' => $enrolled_course->fullname,
|
||||
'role' => $role_shortname,
|
||||
];
|
||||
}
|
||||
|
||||
$publishdata_b64 = base64_encode(json_encode($publish_data));
|
||||
|
||||
try {
|
||||
$dummy_cmid = filter_mediacms_get_dummy_activity(SITEID, $type->id);
|
||||
} catch (Exception $e) {
|
||||
throw new moodle_exception('cannotcreatedummyactivity', 'filter_mediacms');
|
||||
}
|
||||
|
||||
$cm = get_coursemodule_from_id('lti', $dummy_cmid, 0, false, MUST_EXIST);
|
||||
$instance = $DB->get_record('lti', ['id' => $cm->instance], '*', MUST_EXIST);
|
||||
|
||||
$instance->instructorcustomparameters = 'publishdata=' . $publishdata_b64;
|
||||
$instance->name = 'MediaCMS My Media';
|
||||
|
||||
$typeconfig = lti_get_type_type_config($type->id);
|
||||
$content = lti_initiate_login($SITE->id, $dummy_cmid, $instance, $typeconfig, null, $instance->name);
|
||||
|
||||
echo $content;
|
||||
38
lms-plugins/mediacms-moodle/filter/mediacms/my_media.php
Normal file
38
lms-plugins/mediacms-moodle/filter/mediacms/my_media.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* My Media page — renders the Moodle shell with an LTI iframe.
|
||||
*
|
||||
* @package filter_mediacms
|
||||
* @copyright 2026 MediaCMS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
|
||||
global $SITE, $PAGE, $OUTPUT, $USER;
|
||||
|
||||
require_login();
|
||||
|
||||
$context = context_system::instance();
|
||||
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url(new moodle_url('/filter/mediacms/my_media.php'));
|
||||
$PAGE->set_course($SITE);
|
||||
$PAGE->set_pagelayout('mydashboard');
|
||||
$PAGE->set_title(get_string('mymedia', 'filter_mediacms'));
|
||||
$PAGE->set_heading(get_string('mymedia', 'filter_mediacms'));
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
$attr = [
|
||||
'id' => 'contentframe',
|
||||
'src' => (new moodle_url('/filter/mediacms/lti_launch.php'))->out(false),
|
||||
'width' => '100%',
|
||||
'height' => '600px',
|
||||
'allowfullscreen' => 'true',
|
||||
'allow' => 'autoplay *; fullscreen *; encrypted-media *; camera *; microphone *;',
|
||||
'style' => 'border:none;display:block;',
|
||||
];
|
||||
echo html_writer::tag('iframe', '', $attr);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -39,4 +39,16 @@ if ($ADMIN->fulltree) {
|
||||
0,
|
||||
$ltioptions
|
||||
));
|
||||
|
||||
// My Media link placement.
|
||||
$settings->add(new admin_setting_configselect(
|
||||
'filter_mediacms/mymedia_placement',
|
||||
get_string('mymedia_placement', 'filter_mediacms'),
|
||||
get_string('mymedia_placement_desc', 'filter_mediacms'),
|
||||
'top',
|
||||
[
|
||||
'top' => get_string('mymedia_placement_top', 'filter_mediacms'),
|
||||
'user' => get_string('mymedia_placement_user', 'filter_mediacms'),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
130
lti/handlers.py
130
lti/handlers.py
@@ -8,7 +8,9 @@ Provides functions to:
|
||||
- Create and manage LTI sessions
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
|
||||
from allauth.account.models import EmailAddress
|
||||
@@ -200,6 +202,134 @@ def provision_lti_context(platform, claims, resource_link_id):
|
||||
return category, rbac_group, resource_link
|
||||
|
||||
|
||||
# Moodle role shortnames → MediaCMS group roles.
|
||||
_MOODLE_ROLE_TO_GROUP_ROLE = {
|
||||
'student': 'member',
|
||||
'guest': 'member',
|
||||
'teacher': 'manager',
|
||||
'editingteacher': 'manager',
|
||||
'manager': 'manager',
|
||||
'coursecreator': 'manager',
|
||||
'ta': 'contributor',
|
||||
}
|
||||
|
||||
|
||||
def provision_lti_bulk_contexts(platform, user, publish_data_raw):
|
||||
"""
|
||||
Bulk-provision categories, groups, and memberships for every course the
|
||||
user is enrolled in, as reported by the LMS via the custom_publishdata
|
||||
parameter.
|
||||
|
||||
Called on My Media launches where there is no specific course context.
|
||||
Skips the Moodle site course (ID 1).
|
||||
|
||||
Args:
|
||||
platform: LTIPlatform instance
|
||||
user: User instance
|
||||
publish_data_raw: base64-encoded JSON string — list of dicts with
|
||||
keys: id, shortname, fullname, role
|
||||
"""
|
||||
try:
|
||||
# Restore any stripped base64 padding before decoding.
|
||||
padding = 4 - len(publish_data_raw) % 4
|
||||
if padding != 4:
|
||||
publish_data_raw += '=' * padding
|
||||
courses = json.loads(base64.b64decode(publish_data_raw).decode('utf-8'))
|
||||
except Exception as exc:
|
||||
logger.warning('provision_lti_bulk_contexts: failed to decode publishdata: %s', exc)
|
||||
return
|
||||
|
||||
if not isinstance(courses, list):
|
||||
logger.warning('provision_lti_bulk_contexts: publishdata is not a list')
|
||||
return
|
||||
|
||||
for course in courses:
|
||||
try:
|
||||
course_id = str(course.get('id', '')).strip()
|
||||
|
||||
# Always skip the Moodle site course.
|
||||
if not course_id or course_id == '1':
|
||||
continue
|
||||
|
||||
fullname = course.get('fullname', '')
|
||||
shortname = course.get('shortname', '')
|
||||
group_role = _MOODLE_ROLE_TO_GROUP_ROLE.get(course.get('role', 'student'), 'member')
|
||||
|
||||
# ── Category & group ──────────────────────────────────────────
|
||||
# Reuse the same .filter(...).first() pattern as provision_lti_context
|
||||
# so that a real course-specific launch later finds the same record.
|
||||
resource_link = LTIResourceLink.objects.filter(
|
||||
platform=platform,
|
||||
context_id=course_id,
|
||||
).first()
|
||||
|
||||
if resource_link:
|
||||
category = resource_link.category
|
||||
rbac_group = resource_link.rbac_group
|
||||
|
||||
# Keep the category title in sync with the LMS.
|
||||
if fullname and category and category.title != fullname:
|
||||
category.title = fullname
|
||||
category.save(update_fields=['title'])
|
||||
else:
|
||||
category = Category.objects.create(
|
||||
title=fullname or shortname or f'Course {course_id}',
|
||||
description=f'Auto-created from {platform.name}: {fullname}',
|
||||
is_global=False,
|
||||
is_rbac_category=True,
|
||||
is_lms_course=True,
|
||||
lti_platform=platform,
|
||||
lti_context_id=course_id,
|
||||
)
|
||||
|
||||
rbac_group = RBACGroup.objects.create(
|
||||
name=f'{fullname or shortname} ({platform.name})',
|
||||
description=f'LTI course group from {platform.name}',
|
||||
)
|
||||
rbac_group.categories.add(category)
|
||||
|
||||
# Use a synthetic resource_link_id so provision_lti_context
|
||||
# can find and reuse this record when the user later launches
|
||||
# from an actual course page (it searches by platform+context_id
|
||||
# without filtering on resource_link_id).
|
||||
LTIResourceLink.objects.create(
|
||||
platform=platform,
|
||||
context_id=course_id,
|
||||
resource_link_id=f'bulk_{course_id}',
|
||||
context_title=fullname,
|
||||
context_label=shortname,
|
||||
category=category,
|
||||
rbac_group=rbac_group,
|
||||
)
|
||||
|
||||
if rbac_group is None:
|
||||
continue
|
||||
|
||||
# ── Membership ────────────────────────────────────────────────
|
||||
existing = RBACMembership.objects.filter(user=user, rbac_group=rbac_group)
|
||||
if existing.exists():
|
||||
# Upgrade role if the LMS reports a higher privilege than stored.
|
||||
current_role = existing.first().role
|
||||
final_role = get_higher_privilege_group(current_role, group_role)
|
||||
if final_role != current_role:
|
||||
m = existing.first()
|
||||
m.role = final_role
|
||||
m.save(update_fields=['role'])
|
||||
else:
|
||||
try:
|
||||
RBACMembership.objects.create(user=user, rbac_group=rbac_group, role=group_role)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
'provision_lti_bulk_contexts: error processing course %s: %s',
|
||||
course.get('id'),
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
def apply_lti_roles(user, platform, lti_roles, rbac_group):
|
||||
"""
|
||||
Apply role mappings from LTI to MediaCMS
|
||||
|
||||
18
lti/views.py
18
lti/views.py
@@ -36,6 +36,7 @@ from .adapters import DjangoRequest, DjangoSessionService, DjangoToolConfig
|
||||
from .handlers import (
|
||||
apply_lti_roles,
|
||||
create_lti_session,
|
||||
provision_lti_bulk_contexts,
|
||||
provision_lti_context,
|
||||
provision_lti_user,
|
||||
validate_lti_session,
|
||||
@@ -327,13 +328,26 @@ class LaunchView(View):
|
||||
# This ensures filter launches (which are deep linking) have authenticated user
|
||||
user = provision_lti_user(platform, launch_data)
|
||||
|
||||
if 'https://purl.imsglobal.org/spec/lti/claim/context' in launch_data:
|
||||
category, rbac_group, resource_link_obj = provision_lti_context(platform, launch_data, resource_link_id)
|
||||
context_claim = launch_data.get('https://purl.imsglobal.org/spec/lti/claim/context', {})
|
||||
context_id = context_claim.get('id', '')
|
||||
|
||||
# Skip category/group creation for the Moodle site course (ID 1).
|
||||
# My Media launches use Course 1 as a dummy context; real provisioning
|
||||
# for those launches happens below via provision_lti_bulk_contexts.
|
||||
is_site_context = str(context_id) == '1'
|
||||
|
||||
if context_claim and not is_site_context:
|
||||
category, rbac_group, resource_link_obj = provision_lti_context(platform, launch_data, resource_link_id)
|
||||
apply_lti_roles(user, platform, roles, rbac_group)
|
||||
else:
|
||||
resource_link_obj = None
|
||||
|
||||
# Bulk-provision all enrolled courses when the LMS sends custom_publishdata
|
||||
# (only present on My Media launches; transparent on normal course launches).
|
||||
publish_data_raw = custom_claims.get('publishdata') or custom_claims.get('custom_publishdata')
|
||||
if publish_data_raw:
|
||||
provision_lti_bulk_contexts(platform, user, publish_data_raw)
|
||||
|
||||
create_lti_session(request, user, message_launch, platform)
|
||||
|
||||
message_type = launch_data.get('https://purl.imsglobal.org/spec/lti/claim/message_type')
|
||||
|
||||
Reference in New Issue
Block a user