░▒▓████████████████████████████████▓▒░ ░▒▓█ ▓▒░ ░▒▓█ ~ S W A M P ~ ▓▒░ ░▒▓█ ▓▒░ ░▒▓████████████████████████████████▓▒░
pplmvsvm
LOCATION:
/var/www/moodle/public/enrol/lti/classes/local/ltiadvantage/service
☗ ROOT
↻ REFRESH
✎ CARVE FLESH
EDITING: tool_launch_service.php
<?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/>. namespace enrol_lti\local\ltiadvantage\service; use enrol_lti\helper; use enrol_lti\local\ltiadvantage\entity\context; use enrol_lti\local\ltiadvantage\entity\deployment; use enrol_lti\local\ltiadvantage\entity\migration_claim; use enrol_lti\local\ltiadvantage\entity\resource_link; use enrol_lti\local\ltiadvantage\entity\user; use enrol_lti\local\ltiadvantage\repository\application_registration_repository; use enrol_lti\local\ltiadvantage\repository\context_repository; use enrol_lti\local\ltiadvantage\repository\deployment_repository; use enrol_lti\local\ltiadvantage\repository\legacy_consumer_repository; use enrol_lti\local\ltiadvantage\repository\resource_link_repository; use enrol_lti\local\ltiadvantage\repository\user_repository; use Packback\Lti1p3\LtiMessageLaunch; /** * Class tool_launch_service. * * This class handles the launch of a resource by a user, using the LTI Advantage Resource Link Launch. * * See http://www.imsglobal.org/spec/lti/v1p3/#launch-from-a-resource-link * * @package enrol_lti * @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class tool_launch_service { /** @var deployment_repository $deploymentrepo instance of a deployment repository. */ private $deploymentrepo; /** @var application_registration_repository instance of a application_registration repository */ private $registrationrepo; /** @var resource_link_repository instance of a resource_link repository */ private $resourcelinkrepo; /** @var user_repository instance of a user repository*/ private $userrepo; /** @var context_repository instance of a context repository */ private $contextrepo; /** * The tool_launch_service constructor. * * @param deployment_repository $deploymentrepo instance of a deployment_repository. * @param application_registration_repository $registrationrepo instance of an application_registration_repository. * @param resource_link_repository $resourcelinkrepo instance of a resource_link_repository. * @param user_repository $userrepo instance of a user_repository. * @param context_repository $contextrepo instance of a context_repository. */ public function __construct(deployment_repository $deploymentrepo, application_registration_repository $registrationrepo, resource_link_repository $resourcelinkrepo, user_repository $userrepo, context_repository $contextrepo) { $this->deploymentrepo = $deploymentrepo; $this->registrationrepo = $registrationrepo; $this->resourcelinkrepo = $resourcelinkrepo; $this->userrepo = $userrepo; $this->contextrepo = $contextrepo; } /** Get the launch data from the launch. * * @param LtiMessageLaunch $launch the launch instance. * @return \stdClass the launch data. */ private function get_launch_data(LtiMessageLaunch $launch): \stdClass { $launchdata = $launch->getLaunchData(); $data = [ 'platform' => $launchdata['iss'], // The 'aud' property may be an array with one or more values, but can be a string if there is only one value. // https://www.imsglobal.org/spec/security/v1p1#id-token. 'clientid' => is_array($launchdata['aud']) ? $launchdata['aud'][0] : $launchdata['aud'], 'exp' => $launchdata['exp'], 'nonce' => $launchdata['nonce'], 'sub' => $launchdata['sub'], 'roles' => $launchdata['https://purl.imsglobal.org/spec/lti/claim/roles'], 'deploymentid' => $launchdata['https://purl.imsglobal.org/spec/lti/claim/deployment_id'], 'context' => !empty($launchdata['https://purl.imsglobal.org/spec/lti/claim/context']) ? $launchdata['https://purl.imsglobal.org/spec/lti/claim/context'] : null, 'resourcelink' => $launchdata['https://purl.imsglobal.org/spec/lti/claim/resource_link'], 'targetlinkuri' => $launchdata['https://purl.imsglobal.org/spec/lti/claim/target_link_uri'], 'custom' => $launchdata['https://purl.imsglobal.org/spec/lti/claim/custom'] ?? null, 'launchid' => $launch->getLaunchId(), 'user' => [ 'givenname' => !empty($launchdata['given_name']) ? $launchdata['given_name'] : null, 'familyname' => !empty($launchdata['family_name']) ? $launchdata['family_name'] : null, 'name' => !empty($launchdata['name']) ? $launchdata['name'] : null, 'email' => !empty($launchdata['email']) ? $launchdata['email'] : null, 'picture' => !empty($launchdata['picture']) ? $launchdata['picture'] : null, ], 'ags' => $launchdata['https://purl.imsglobal.org/spec/lti-ags/claim/endpoint'] ?? null, 'nrps' => $launchdata['https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice'] ?? null, 'lti1p1' => $launchdata['https://purl.imsglobal.org/spec/lti/claim/lti1p1'] ?? null ]; return (object) $data; } /** * Get a context instance from the launch data. * * @param \stdClass $launchdata launch data. * @param deployment $deployment the deployment to which the context belongs. * @return context the context instance. */ private function context_from_launchdata(\stdClass $launchdata, deployment $deployment): context { if ($context = $this->contextrepo->find_by_contextid($launchdata->context['id'], $deployment->get_id())) { // The context has been mapped, just update it. $context->set_types($launchdata->context['type']); } else { // Map a new context. $context = $deployment->add_context($launchdata->context['id'], $launchdata->context['type']); } return $context; } /** * Get a resource_link from the launch data. * * @param \stdClass $launchdata the launch data. * @param \stdClass $resource the resource to which the resource link refers. * @param deployment $deployment the deployment to which the resource_link belongs. * @param context|null $context optional context in which the resource_link lives, null if not needed. * @return resource_link the resource_link instance. */ private function resource_link_from_launchdata(\stdClass $launchdata, \stdClass $resource, deployment $deployment, ?context $context): resource_link { if ($resourcelink = $this->resourcelinkrepo->find_by_deployment($deployment, $launchdata->resourcelink['id'])) { // Resource link exists, so update it. if (isset($context)) { $resourcelink->set_contextid($context->get_id()); } // A resource link may have been updated, via content item selection, to refer to a different resource. if ($resourcelink->get_resourceid() != $resource->id) { $resourcelink->set_resourceid($resource->id); } } else { // Create a new resource link. $resourcelink = $deployment->add_resource_link( $launchdata->resourcelink['id'], $resource->id, $context ? $context->get_id() : null ); } // Add the AGS configuration for the resource link. // See: http://www.imsglobal.org/spec/lti-ags/v2p0#assignment-and-grade-service-claim. if ($launchdata->ags && (!empty($launchdata->ags['lineitems']) || !empty($launchdata->ags['lineitem']))) { $resourcelink->add_grade_service( !empty($launchdata->ags['lineitems']) ? new \moodle_url($launchdata->ags['lineitems']) : null, !empty($launchdata->ags['lineitem']) ? new \moodle_url($launchdata->ags['lineitem']) : null, $launchdata->ags['scope'] ); } // NRPS. if ($launchdata->nrps) { $resourcelink->add_names_and_roles_service( new \moodle_url($launchdata->nrps['context_memberships_url']), $launchdata->nrps['service_versions'] ); } return $resourcelink; } /** * Get an lti user instance from the launch data. * * @param \stdClass $user the moodle user object. * @param \stdClass $launchdata the launch data. * @param \stdClass $resource the resource to which the user belongs. * @param resource_link $resourcelink the resource_link from which the user originated. * @return user the user instance. */ private function lti_user_from_launchdata(\stdClass $user, \stdClass $launchdata, \stdClass $resource, resource_link $resourcelink): user { // Find the user based on the unique-to-the-issuer 'sub' value. if ($ltiuser = $this->userrepo->find_single_user_by_resource($user->id, $resource->id)) { // User exists, so update existing based on resource data which may have changed. $ltiuser->set_resourcelinkid($resourcelink->get_id()); $ltiuser->set_lang($resource->lang); $ltiuser->set_city($resource->city); $ltiuser->set_country($resource->country); $ltiuser->set_institution($resource->institution); $ltiuser->set_timezone($resource->timezone); $ltiuser->set_maildisplay($resource->maildisplay); } else { // Create the lti user. $ltiuser = $resourcelink->add_user( $user->id, $launchdata->sub, $resource->lang, $resource->city ?? '', $resource->country ?? '', $resource->institution ?? '', $resource->timezone ?? '', $resource->maildisplay ?? null, ); } $ltiuser->set_lastaccess(time()); return $ltiuser; } /** * Get the migration claim from the launch data, or null if not found. * * @param \stdClass $launchdata the launch data. * @return migration_claim|null the claim instance if present in the launch data, else null. */ private function migration_claim_from_launchdata(\stdClass $launchdata): ?migration_claim { if (!isset($launchdata->lti1p1)) { return null; } // Despite the spec requiring the oauth_consumer_key field be present in the migration claim: // (see https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key), // Platforms may omit this field making migration impossible. // E.g. for Canvas launches taking place after an assignment_selection placement. if (empty($launchdata->lti1p1['oauth_consumer_key'])) { return null; } return new migration_claim($launchdata->lti1p1, $launchdata->deploymentid, $launchdata->platform, $launchdata->clientid, $launchdata->exp, $launchdata->nonce, new legacy_consumer_repository()); } /** * Check whether the launch user has an admin role. * * @param \stdClass $launchdata the launch data. * @return bool true if the user is admin, false otherwise. */ private function user_is_admin(\stdClass $launchdata): bool { // See: http://www.imsglobal.org/spec/lti/v1p3/#role-vocabularies. if ($launchdata->roles) { $adminroles = [ 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator', 'http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator' ]; foreach ($adminroles as $validrole) { if (in_array($validrole, $launchdata->roles)) { return true; } } } return false; } /** * Check whether the launch user is an instructor. * * @param \stdClass $launchdata the launch data. * @param bool $includelegacy whether to also consider legacy simple names as valid roles. * @return bool true if the user is an instructor, false otherwise. */ private function user_is_staff(\stdClass $launchdata, bool $includelegacy = false): bool { // See: http://www.imsglobal.org/spec/lti/v1p3/#role-vocabularies. // This method also provides support for (legacy, deprecated) simple names for context roles. // I.e. 'ContentDeveloper' may be supported. if ($launchdata->roles) { $staffroles = [ 'http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper', 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor', 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant' ]; if ($includelegacy) { $staffroles[] = 'ContentDeveloper'; $staffroles[] = 'Instructor'; $staffroles[] = 'Instructor#TeachingAssistant'; } foreach ($staffroles as $validrole) { if (in_array($validrole, $launchdata->roles)) { return true; } } } return false; } /** * Handles the use case "A user launches the tool so they can view an external resource". * * @param \stdClass $user the Moodle user record, obtained via the auth_lti authentication process. * @param LtiMessageLaunch $launch the launch data. * @return array array containing [int $userid, \stdClass $resource] * @throws \moodle_exception if launch problems are encountered. */ public function user_launches_tool(\stdClass $user, LtiMessageLaunch $launch): array { $launchdata = $this->get_launch_data($launch); if (!$registration = $this->registrationrepo->find_by_platform($launchdata->platform, $launchdata->clientid)) { throw new \moodle_exception('ltiadvlauncherror:invalidregistration', 'enrol_lti', '', [$launchdata->platform, $launchdata->clientid]); } if (!$deployment = $this->deploymentrepo->find_by_registration($registration->get_id(), $launchdata->deploymentid)) { throw new \moodle_exception('ltiadvlauncherror:invaliddeployment', 'enrol_lti', '', [$launchdata->deploymentid]); } $resourceuuid = $launchdata->custom['id'] ?? null; if (empty($resourceuuid)) { throw new \moodle_exception('ltiadvlauncherror:missingid', 'enrol_lti'); } $resource = array_values(helper::get_lti_tools(['uuid' => $resourceuuid])); $resource = $resource[0] ?? null; if (empty($resource) || $resource->status != ENROL_INSTANCE_ENABLED) { throw new \moodle_exception('ltiadvlauncherror:invalidid', 'enrol_lti', '', $resourceuuid); } // Update the deployment with the legacy consumer_key information, allowing migration of users to take place in future // names and roles syncs. if ($migrationclaim = $this->migration_claim_from_launchdata($launchdata)) { $deployment->set_legacy_consumer_key($migrationclaim->get_consumer_key()); $this->deploymentrepo->save($deployment); } // Save the context, if that claim is present. $context = null; if ($launchdata->context) { $context = $this->context_from_launchdata($launchdata, $deployment); $context = $this->contextrepo->save($context); } // Save the resource link for the tool deployment. $resourcelink = $this->resource_link_from_launchdata($launchdata, $resource, $deployment, $context); $resourcelink = $this->resourcelinkrepo->save($resourcelink); // Save the user launching the resource link. $ltiuser = $this->lti_user_from_launchdata($user, $launchdata, $resource, $resourcelink); $ltiuser = $this->userrepo->save($ltiuser); // Set the frame embedding mode, which controls the display of blocks and nav when launching. global $SESSION; $context = \context::instance_by_id($resource->contextid); $isforceembed = $launchdata->custom['force_embed'] ?? false; $isinstructor = $this->user_is_staff($launchdata, true) || $this->user_is_admin($launchdata); $isforceembed = $isforceembed || ($context->contextlevel == CONTEXT_MODULE && !$isinstructor); if ($isforceembed) { $SESSION->forcepagelayout = 'embedded'; } else { unset($SESSION->forcepagelayout); } // Enrol the user in the course with no role. $result = helper::enrol_user($resource, $ltiuser->get_localid()); if ($result !== helper::ENROLMENT_SUCCESSFUL) { throw new \moodle_exception($result, 'enrol_lti'); } // Give the user the role in the given context. $roleid = $isinstructor ? $resource->roleinstructor : $resource->rolelearner; role_assign($roleid, $ltiuser->get_localid(), $resource->contextid); return [$ltiuser->get_localid(), $resource]; } }
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 application_registration_service.php
PHP
7 KB
2026-02-11 01:44
EDIT
📄 tool_deployment_service.php
PHP
5.5 KB
2026-02-11 01:44
EDIT
📄 tool_launch_service.php
PHP
17.5 KB
2026-02-11 01:44
EDIT