diff options
22 files changed, 453 insertions, 287 deletions
diff --git a/app/controllers/course/lti.php b/app/controllers/course/lti.php index d167a9e..4658589 100644 --- a/app/controllers/course/lti.php +++ b/app/controllers/course/lti.php @@ -13,6 +13,7 @@ use OAT\Library\Lti1p3DeepLinking\Message\Launch\Builder\DeepLinkingLaunchReques use Studip\LTI13a\PlatformManager; use Studip\LTI13a\Registration; use Studip\LTI13a\RegistrationManager; +use OAT\Library\Lti1p3Core\Message\Payload\MessagePayloadInterface\MessagePayloadInterface; /** * course/lti.php - LTI consumer API for Stud.IP @@ -119,14 +120,23 @@ class Course_LtiController extends StudipController } Helpbar::get()->addPlainText('', _('Auf dieser Seite können Sie externe Anwendungen einbinden, sofern diese den LTI-Standard (Version 1.x) unterstützen.')); - if (Request::get('deployment_id')) { + + //Check for error messages: + if (Request::get('deployment_id') && (Request::submitted('lti_msg') || Request::submitted('lti_errormsg'))) { $deployment = LtiDeployment::find(Request::get('deployment_id')); if ($deployment) { - if (Request::get('lti_msg')) { - PageLayout::postInfo(htmlReady($deployment->title . ': ' . Request::get('lti_msg'))); - } - if (Request::get('lti_errormsg')) { - PageLayout::postError(htmlReady($deployment->title . ': ' . Request::get('lti_errormsg'))); + //Get the resource link for the deployment and display the messages: + $link = \LtiResourceLink::findOneBySQL( + "`deployment_id` = :deployment_id AND `course_id` = :course_id", + ['deployment_id' => $deployment->id, 'course_id' => $this->course_id] + ); + if ($link) { + if (Request::get('lti_msg')) { + PageLayout::postInfo(htmlReady($link->title . ': ' . Request::get('lti_msg'))); + } + if (Request::get('lti_errormsg')) { + PageLayout::postError(htmlReady($link->title . ': ' . Request::get('lti_errormsg'))); + } } } } @@ -138,7 +148,11 @@ class Course_LtiController extends StudipController $this->global_tool_deployments = LtiDeployment::findBySQL( "JOIN `lti_tools` ON `lti_deployments`.`tool_id` = `lti_tools`.`id` - WHERE `lti_tools`.`lti_version` = '1.3a' AND `lti_tools`.`range_id` = 'global' ORDER BY `lti_tools`.`name` ASC" + WHERE + `lti_deployments`.`purpose` = 'general' + AND `lti_tools`.`lti_version` = '1.3a' + AND `lti_tools`.`range_id` = 'global' + ORDER BY `lti_tools`.`name` ASC" ); if (!$this->global_tool_deployments) { @@ -267,11 +281,15 @@ class Course_LtiController extends StudipController //LTI 1.3a $this->lti13a_mode = true; - $return_url = !isset($this->resource_link->deployment->options['document_target']) ? - URLHelper::getURL($GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/course/lti', ['deployment_id' => $this->resource_link->deployment_id]) : ''; - $document_target = isset($this->resource_link->deployment->options['document_target']) ? 'iframe' : 'window'; + $return_url = URLHelper::getURL($GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/course/lti', ['deployment_id' => $this->resource_link->deployment_id]); + $document_target = 'window'; + if (!empty($this->resource_link->options['document_target'])) { + $return_url = $this->resource_link->options['document_target']; + $document_target = 'iframe'; + } + $locale = str_replace('_', '-', $_SESSION['_language']); - $registration = new Registration($this->resource_link->deployment->tool); + $registration = new Registration($this->resource_link->deployment->tool, $this->resource_link); $builder = new LtiResourceLinkLaunchRequestBuilder(); //The AGS URLs need several parameters: @@ -316,7 +334,7 @@ class Course_LtiController extends StudipController $this->url_for('lti/ags/line_item', $ags_url_parameters) ) ], - $this->resource_link->deployment->getCustomLtiParameterArray(), + $this->resource_link->getCustomLtiParameterArray(), ) ); } else { @@ -420,7 +438,7 @@ class Course_LtiController extends StudipController } /** - * Select a tool for adding a block via ContentItemSelectionRequest. + * Offers tool selection for LTI deep linking. */ public function add_link_action() { @@ -434,67 +452,69 @@ class Course_LtiController extends StudipController } /** - * Dispatch a ContentItemSelectionRequest to a specified LTI tool. + * Prepares the tool selected in the add_link action for being included in the course + * and displays the platform configuration that must be added in the LTI tool. */ - public function select_link_action($deployment_id = '') + public function select_link_action() { - $this->deployment = null; - if ($deployment_id) { - $this->deployment = LtiDeployment::find($deployment_id); - if (!$this->deployment) { - PageLayout::postError(_('Die Einbindung des LTI-Tools wurde nicht gefunden!')); - return; - } - if ($this->deployment->course_id !== $this->course_id) { - PageLayout::postError(_('Die Einbindung des LTI-Tools ist nicht für diese Veranstaltung bestimmt.')); - return; - } - if (empty($this->deployment->options['unfinished_deep_linking'])) { - PageLayout::postError(_('Die Einbindung des LTI-Tools ist bereits abgeschlossen.')); - return; - } - } - $this->tool = LtiTool::find(Request::int('tool_id')); if (!$this->tool) { PageLayout::postError(_('Das ausgewählte LTI-Tool wurde nicht gefunden.')); - $this->redirect('course/lti/add_link'); + $this->relocate('course/lti/add_link'); return; } if (!$this->tool->deep_linking) { PageLayout::postError(_('Das ausgewählte LTI-Tool unterstützt kein Deep Linking.')); - $this->redirect('course/lti/add_link'); + $this->relocate('course/lti/add_link'); return; } + + //Create a deployment for deep linking: + $this->deployment = new LtiDeployment(); + $this->deployment->tool_id = $this->tool->id; + $this->deployment->purpose = 'deep_linking'; + if ($this->deployment->store()) { + //Create an LTI resource link for the course: + $this->link = new \LtiResourceLink(); + $this->link->deployment_id = $this->deployment->id; + $this->link->course_id = $this->course_id; + $this->link->options = ['unfinished_deep_linking' => 'true']; + if (!$this->link->store()) { + PageLayout::postError(_('Die Einbindung des LTI-Tools in die Veranstaltung ist fehlgeschlagen.')); + $this->relocate('course/lti/add_link'); + } + } else { + PageLayout::postError(_('Es konnte kein LTI-Deployment für LTI Deep Linking erstellt werden.')); + $this->relocate('course/lti/add_link'); + } } - public function process_select_link_action($deployment_id = '') + /** + * Proceeds after the select_link action by switching to the LTI tool for + * selecting the items from the deep linked tool that shall be available in the Stud.IP course. + */ + public function process_select_link_action($link_id = '') { CSRFProtection::verifyUnsafeRequest(); - $this->deployment = null; - if ($deployment_id) { - $this->deployment = LtiDeployment::find($deployment_id); - if (!$this->deployment) { - PageLayout::postError(_('Die Einbindung des LTI-Tools wurde nicht gefunden!')); - return; - } - if ($this->deployment->course_id !== $this->course_id) { - PageLayout::postError(_('Die Einbindung des LTI-Tools ist nicht für diese Veranstaltung bestimmt.')); - return; - } - if (empty($this->deployment->options['unfinished_deep_linking'])) { - PageLayout::postError(_('Die Einbindung des LTI-Tools ist bereits abgeschlossen.')); - return; - } + $this->link = \LtiResourceLink::find($link_id); + if (!$this->link) { + PageLayout::postError(_('Die Einbindung des LTI-Tools wurde nicht gefunden.')); + $this->relocate('course/lti/add_link'); + return; } - - $this->tool = null; - if ($this->deployment) { - $this->tool = $this->deployment->tool; - } else { - $this->tool = LtiTool::find(Request::int('tool_id')); + if ($this->link->course_id !== $this->course_id) { + PageLayout::postError(_('Die Einbindung des LTI-Tools ist nicht für diese Veranstaltung bestimmt.')); + $this->relocate('course/lti/add_link'); + return; + } + if (empty($this->link->options['unfinished_deep_linking'])) { + PageLayout::postError(_('Die Einbindung des LTI-Tools ist bereits abgeschlossen.')); + $this->relocate('course/lti/add_link'); + return; } + + $this->tool = $this->link->deployment->tool ?? null; if (!$this->tool) { PageLayout::postError(_('Das ausgewählte LTI-Tool wurde nicht gefunden.')); $this->redirect('course/lti/add_link'); @@ -508,37 +528,20 @@ class Course_LtiController extends StudipController if ($this->tool->lti_version === '1.3a') { //LTI 1.3a - if ($this->deployment) { - $builder = new DeepLinkingLaunchRequestBuilder(); - $message = $builder->buildDeepLinkingLaunchRequest( - PlatformManager::getDeepLinkingConfiguration($this->tool->id), - new Registration($this->deployment->tool), - $GLOBALS['user']->id, - null, - $this->deployment->id, - [PlatformManager::getLtiRoleClaimForStudipRole($GLOBALS['perm']->get_studip_perm($this->course_id))] - ); - $this->render_text($message->toHtmlRedirectForm()); - } else { - //Build an LTI deployment object and mark it as not configured - //so that it can be displayed differently in the UI. - $this->deployment = new LtiDeployment(); - $this->deployment->tool_id = $this->tool->id; - $this->deployment->course_id = $this->course_id; - $this->deployment->title = $this->tool->name; - $this->deployment->options = ['unfinished_deep_linking' => true]; - if ($this->deployment->store() !== false) { - //Display the tool deployment data so that the user can enter - //them in the LTI tool. - PageLayout::postInfo( - _('Bitte tragen Sie die Daten zur Einbindung im LTI-Tool ein bevor sie fortfahren.') - ); - } - } + $builder = new DeepLinkingLaunchRequestBuilder(); + $message = $builder->buildDeepLinkingLaunchRequest( + PlatformManager::getDeepLinkingConfiguration($this->link->id, $this->course_id), + new Registration($this->tool, $this->link), + $GLOBALS['user']->id, + null, + $this->link->deployment_id, + [PlatformManager::getLtiRoleClaimForStudipRole($GLOBALS['perm']->get_studip_perm($this->course_id))] + ); + $this->render_text($message->toHtmlRedirectForm()); } else { //LTI 1.0/1.1 $custom_parameters = explode("\n", $this->tool->custom_parameters); - $content_item_return_url = $this->url_for('course/lti/save_link/' . $this->tool->id); + $content_item_return_url = $this->url_for('course/lti/save_link/' . $this->link->id); // set up ContentItemSelectionRequest $lti_link = new LtiLink($this->tool->launch_url, $this->tool->consumer_key, $this->tool->consumer_secret, $this->tool->oauth_signature_method); @@ -570,14 +573,31 @@ class Course_LtiController extends StudipController } /** - * Create a new LTI content block for the specified tool id. + * Handles the jump back from the LTI tool into Stud.IP and finishes the integration + * of a deep linked LTI tool into a Stud.IP course. * - * @param int $tool_id tool id + * @param int $link_id tool id */ - public function save_link_action($tool_id) + public function save_link_action($link_id) { - $tool = LtiTool::find($tool_id); + $this->link = \LtiResourceLink::find($link_id); + if (!$this->link) { + PageLayout::postError(_('Die Einbindung des LTI-Tools wurde nicht gefunden!')); + $this->relocate('course/lti/add_link'); + return; + } + if ($this->link->course_id !== $this->course_id) { + PageLayout::postError(_('Die Einbindung des LTI-Tools ist nicht für diese Veranstaltung bestimmt.')); + $this->relocate('course/lti/add_link'); + return; + } + if (empty($this->link->options['unfinished_deep_linking'])) { + PageLayout::postError(_('Die Einbindung des LTI-Tools ist bereits abgeschlossen.')); + $this->relocate('course/lti/add_link'); + return; + } + $tool = $this->link->deployment->tool ?? null; if (!$tool) { PageLayout::postError(_('Das ausgewählte LTI-Tool wurde nicht gefunden.')); $this->redirect('course/lti/add_link'); @@ -592,14 +612,17 @@ class Course_LtiController extends StudipController if ($tool->lti_version === '1.3a') { //LTI 1.3a + $reg_man = new RegistrationManager(); + $reg_man->setResourceLink($this->link); + $validator = new PlatformLaunchValidator( - new RegistrationManager(), + $reg_man, new NonceRepository(Studip\Cache\Factory::getCache()) ); $result = $validator->validateToolOriginatingLaunch($this->getPsrRequest()); if ($result->hasError()) { PageLayout::postError($result->getError()); - $this->redirect('course/lti/add_link'); + $this->redirect('course/lti/index'); return; } $all_lti_resources = (new ResourceCollectionFactory())->createFromClaim( @@ -608,35 +631,12 @@ class Course_LtiController extends StudipController $lti_resource_links = $all_lti_resources->getByType(LtiResourceLinkInterface::TYPE); if (count($lti_resource_links) > 0) { - $use_first_link = true; foreach ($lti_resource_links as $lti_resource_link) { - $deployment = LtiDeployment::findOneBySQL( - "JOIN `lti_resource_links` - ON `lti_deployments`.`id` = `lti_resource_links`.`deployment_id` - WHERE - `lti_deployments`.`tool_id` = :tool_id AND `lti_resource_links`.`course_id` = :course_id - AND `lti_deployments`.`options` LIKE '%unfinished_deep_linking%=%true'", - ['tool_id' => $this->tool->id, 'course_id' => $this->range_id] - ); - $use_first_link = false; - if (!$deployment) { - //If this is the first link, the deployment has been removed. - //In that case and if it is not the first link, a new deployment - //has to be created. - $deployment = new LtiDeployment(); - $deployment->tool_id = $tool->id; - $deployment->title = $tool->name; - } - $deployment->launch_url = $lti_resource_link->getUrl(); - if (!empty($deployment->options['unfinished_deep_linking'])) { - unset($deployment->options['unfinished_deep_linking']); - } - if ($deployment->store()) { - $link = new \LtiResourceLink(); - $link->deployment_id = $deployment->id; - $link->course_id = $this->range_id; - $link->store(); + $this->link->launch_url = $lti_resource_link->getUrl(); + if (!empty($this->link->options['unfinished_deep_linking'])) { + unset($this->link->options['unfinished_deep_linking']); } + $this->link->store(); } } } else { @@ -656,7 +656,7 @@ class Course_LtiController extends StudipController $lti_data = new LtiDeployment(); $lti_data->title = (string) $item['title']; $lti_data->description = Studip\Markup::purifyHtml(Studip\Markup::markAsHtml($item['text'])); - $lti_data->tool_id = $tool_id; + $lti_data->tool_id = $tool->id; $lti_data->launch_url = (string) ($item['url'] ?? ''); $options = []; if (is_array($item['custom'])) { @@ -682,7 +682,7 @@ class Course_LtiController extends StudipController } } - if ($lti_errormsg) { + if (!empty($lti_errormsg)) { PageLayout::postError($lti_errormsg); } @@ -819,9 +819,9 @@ class Course_LtiController extends StudipController */ public function outcome_action($id) { - $lti_data = LtiDeployment::find($id); + $lti_data = \LtiResourceLink::find($id); - if (!Studip\OAuth1::verifyRequest($this->getPsrRequest(), $lti_data->getConsumerSecret(), '')) { + if (!Studip\OAuth1::verifyRequest($this->getPsrRequest(), $lti_data->deployment->getConsumerSecret(), '')) { throw new Exception('Could not verify request.'); } @@ -880,21 +880,17 @@ class Course_LtiController extends StudipController Navigation::activateItem('/course/lti/grades'); if ($this->edit_perm) { - $this->lti_data_array = LtiDeployment::findBySQL( - "JOIN `lti_resource_links` - ON `lti_deployments`.`id` = `lti_resource_links`.`deployment_id` - WHERE `lti_resource_links`.`course_id` = :course_id - ORDER BY `lti_resource_links`.`position`", + $this->lti_data_array = \LtiResourceLink::findBySQL( + "`course_id` = :course_id + ORDER BY `position`", ['course_id' => $this->course_id] ); } else { //Only load those deployments that are fully configured: - $this->lti_data_array = LtiDeployment::findBySQL( - "JOIN `lti_resource_links` - ON `lti_deployments`.`id` = `lti_resource_links`.`deployment_id` - WHERE `lti_resource_links`.`course_id` = :course_id - AND (`lti_deployments`.`options` IS NULL OR `lti_deployments`.`options` NOT LIKE '%unfinished_deep_linking%') - ORDER BY `lti_resource_links`.`position`", + $this->lti_data_array = \LtiResourceLink::findBySQL( + "`course_id` = :course_id + AND (`options` IS NULL OR `options` NOT LIKE '%unfinished_deep_linking%') + ORDER BY `position`", ['course_id' => $this->course_id] ); } @@ -926,10 +922,10 @@ class Course_LtiController extends StudipController public function export_grades_action() { if ($this->edit_perm) { - $lti_data_array = LtiDeployment::findByCourse_id($this->course_id, 'ORDER BY position'); + $lti_data_array = \LtiResourceLink::findByCourse_id($this->course_id, 'ORDER BY position'); } else { //Only load those deployments that are fully configured: - $lti_data_array = LtiDeployment::findBySQL( + $lti_data_array = \LtiResourceLink::findBySQL( "`course_id` = :course_id AND (`options` IS NULL OR `options` NOT LIKE '%unfinished_deep_linking%') ORDER BY `position`", ['course_id' => $this->course_id] diff --git a/app/controllers/lti/tool.php b/app/controllers/lti/tool.php index fae276e..61ca37b 100644 --- a/app/controllers/lti/tool.php +++ b/app/controllers/lti/tool.php @@ -35,12 +35,13 @@ class Lti_ToolController extends AuthenticatedController { //$this->range_id and $this->tool are created in the before-filter. if ($this->range_id !== 'global') { - $this->deployment = LtiDeployment::findOneBySQL( - 'JOIN `lti_resource_links` + $link_id = Request::get('link_id'); + $this->link = \LtiResourceLink::findOneBySQL( + 'JOIN `lti_deployments` ON `lti_deployments`.`id` = `lti_resource_links`.`deployment_id` WHERE - `lti_deployments`.`tool_id` = :tool_id AND `lti_resource_links`.`course_id` = :range_id', - ['tool_id' => $this->tool->id, 'range_id' => $this->range_id] + `lti_deployments`.`tool_id` = :tool_id AND `lti_resource_links`.`id` = :link_id', + ['tool_id' => $this->tool->id, 'link_id' => $link_id] ); } } @@ -74,12 +75,9 @@ class Lti_ToolController extends AuthenticatedController PageLayout::postWarning(_('Bitte beachten Sie das geltende europäische Datenschutzrecht (DSGVO)!')); } elseif (!$this->tool->isEditableByUser()) { throw new AccessDeniedException(); - } else { - //The tool is old and editable by the user. Check if a deployment exists and load it. - $this->deployment = LtiDeployment::findOneBySQL( - "`tool_id` = :tool_id ORDER BY `mkdate` ASC", - ['tool_id' => $this->tool->id] - ); + } elseif (Request::get('link_id')) { + //The tool is old and editable by the user. Check if a link exists and load it. + $this->link = \LtiResourceLink::find(Request::get('link_id')); } if (Request::isPost()) { @@ -93,6 +91,10 @@ class Lti_ToolController extends AuthenticatedController protected function saveTool(): void { CSRFProtection::verifyUnsafeRequest(); + $this->link = null; + if (Request::get('link_id')) { + $this->link = \LtiResourceLink::find(Request::get('link_id')); + } //Note: $this->tool is created in the before_filter. $new_tool = $this->tool->isNew(); $this->tool->name = trim(Request::get('name')); @@ -118,10 +120,10 @@ class Lti_ToolController extends AuthenticatedController $this->tool->custom_parameters = trim(Request::get('custom_parameters')); $tool_public_key = trim(Request::get('tool_public_key')); - //Check if the tool has a deployment. If so, use it. Otherwise, create a new deployment. + //Check if the tool has a general deployment. If so, use it. Otherwise, create a new deployment. if (!$new_tool) { $this->deployment = LtiDeployment::findOneBySQL( - "`tool_id` = :tool_id ORDER BY `mkdate` ASC", + "`tool_id` = :tool_id AND `purpose` = 'general' ORDER BY `mkdate` ASC", ['tool_id' => $this->tool->id] ); } @@ -131,18 +133,6 @@ class Lti_ToolController extends AuthenticatedController $this->deployment->tool_id = $this->tool->id; } } - $this->deployment->description = trim(Request::get('description')); - $this->deployment->title = $this->tool->name; - $this->deployment->launch_url = $this->tool->launch_url; - $document_target = trim(Request::get('document_target')); - if ($document_target === 'iframe') { - if (!is_array($this->deployment->options)) { - $this->deployment->options = []; - } - $this->deployment->options['document_target'] = $document_target; - } elseif (isset($this->deployment->options['document_target'])) { - unset($this->deployment->options['document_target']); - } $errors = $this->tool->validate(); if ($errors) { @@ -166,22 +156,34 @@ class Lti_ToolController extends AuthenticatedController return; } if ($this->range_id !== 'global') { - $resource_link_exists = false; + $resource_link = null; if (!$new_tool) { //Create an LTI resource link, if it doesn't exist yet: - $resource_link_exists = \LtiResourceLink::countBySQL( + $resource_link = \LtiResourceLink::findOneBySQL( "`deployment_id` = :deployment_id AND `course_id` = :course_id", ['deployment_id' => $this->deployment->id, 'course_id' => $this->range_id] - ) > 0; + ); } - if (!$resource_link_exists) { + if (!$resource_link) { //Either the tool has just been created or the tool existed and it wasn't yet //linked to the course. In those both cases, we have to create a new LTI resource link. $resource_link = new \LtiResourceLink(); $resource_link->deployment_id = $this->deployment->id; $resource_link->course_id = $this->range_id; - $resource_link->store(); } + $resource_link->description = trim(Request::get('description')); + $resource_link->title = $this->tool->name; + $resource_link->launch_url = $this->tool->launch_url; + $document_target = trim(Request::get('document_target')); + if ($document_target === 'iframe') { + if (!is_array($resource_link->options)) { + $resource_link->options = []; + } + $resource_link->options['document_target'] = $document_target; + } elseif (isset($resource_link->options['document_target'])) { + unset($resource_link->options['document_target']); + } + $resource_link->store(); } if ($this->tool->lti_version === '1.3a' && $tool_public_key) { if (!$this->tool->updatePublicKey($tool_public_key)) { @@ -189,6 +191,8 @@ class Lti_ToolController extends AuthenticatedController _('Der öffentliche Schlüssel des LTI-Tools konnte nicht gespeichert werden.') ); } + } else { + Keyring::deleteBySQL("`range_type` = 'lti_tool' AND `range_id` = :tool_id", ['tool_id' => $this->tool->id]); } PageLayout::postSuccess(_('Das LTI-Tool wurde gespeichert.')); diff --git a/app/views/admin/lti/index.php b/app/views/admin/lti/index.php index f047add..83faf3a 100644 --- a/app/views/admin/lti/index.php +++ b/app/views/admin/lti/index.php @@ -17,6 +17,7 @@ <col style="width: 5%;"> <col style="width: 5%;"> <col style="width: 5%;"> + <col style="width: 5%;"> </colgroup> <thead> @@ -26,6 +27,7 @@ <th><?= _('Consumer-Key') ?></th> <th><?= _('LTI-Version') ?></th> <th><?= _('Deployment-ID') ?></th> + <th><?= _('Deep Links') ?></th> <th><?= _('Links') ?></th> <th class="actions"><?= _('Aktionen') ?></th> </tr> @@ -49,9 +51,25 @@ <td> <? //Each tool should only have one deployment-ID: - $deployment = LtiDeployment::findOneByTool_id($tool->id); + $deployments = LtiDeployment::findBySQL( + "`tool_id` = :tool_id AND `purpose` = 'general'", + ['tool_id' => $tool->id] + ); + $deployment_ids = []; + foreach ($deployments as $deployment) { + $deployment_ids[] = $deployment->id; + } ?> - <?= htmlReady($deployment->id ?? '') ?> + <?= htmlReady(implode(', ', $deployment_ids)) ?> + <? if (count($deployment_ids) > 1) : ?> + <?= tooltipIcon(_('Dieses Tool hat mehrere Deployment-IDs zur generellen Nutzung!')) ?> + <? endif ?> + </td> + <td> + <?= htmlReady(LtiDeployment::countBySQL( + "`tool_id` = :tool_id AND `purpose` = 'deep_linking'", + ['tool_id' => $tool->id] + )) ?> </td> <td> <?= \LtiResourceLink::countBySql( diff --git a/app/views/course/lti/add_link.php b/app/views/course/lti/add_link.php index dc479a2..c5fd555 100644 --- a/app/views/course/lti/add_link.php +++ b/app/views/course/lti/add_link.php @@ -3,7 +3,7 @@ <?= _('Auswahl des externen Tools') ?> <select name="tool_id"> <? foreach ($tools as $tool): ?> - <option value="<?= $tool->id ?>"><?= htmlReady($tool->name) ?></option> + <option value="<?= htmlReady($tool->id) ?>"><?= htmlReady($tool->name) ?></option> <? endforeach ?> </select> </label> diff --git a/app/views/course/lti/consent.php b/app/views/course/lti/consent.php index be78d5d..a6a07e9 100644 --- a/app/views/course/lti/consent.php +++ b/app/views/course/lti/consent.php @@ -53,7 +53,7 @@ <?= _('Ihr Profilbild') ?> </label> </fieldset> - <?= $this->render_partial('lti/_deployment_user_info', ['deployment' => $resource_link->deployment]) ?> + <?= $this->render_partial('lti/_link_user_info', ['link' => $resource_link]) ?> <fieldset> <legend><?= _('Bestätigung') ?></legend> <label> diff --git a/app/views/course/lti/grades.php b/app/views/course/lti/grades.php index e9b0b40..48df9e9 100644 --- a/app/views/course/lti/grades.php +++ b/app/views/course/lti/grades.php @@ -27,7 +27,7 @@ </td> <? foreach ($lti_data_array as $lti_data): ?> <td style="text-align: right;"> - <? if ($grade = $lti_data->grades->findOneBy('user_id', $member->user_id)): ?> + <? if ($grade = $lti_data->deployment->grades->findOneBy('user_id', $member->user_id)): ?> <?= sprintf('%.0f%%', $grade->score * 100) ?> <? else: ?> – diff --git a/app/views/course/lti/grades_user.php b/app/views/course/lti/grades_user.php index fb041c5..04ce429 100644 --- a/app/views/course/lti/grades_user.php +++ b/app/views/course/lti/grades_user.php @@ -16,7 +16,7 @@ <tr> <td><?= htmlReady($lti_data->title) ?></td> <td style="text-align: right;"> - <? if ($grade = LtiGrade::find([$lti_data->id, $GLOBALS['user']->id])): ?> + <? if ($grade = LtiGrade::find([$lti_data->deployment_id, $GLOBALS['user']->id])): ?> <?= sprintf('%.0f%%', $grade->score * 100) ?> <? else: ?> – diff --git a/app/views/course/lti/index.php b/app/views/course/lti/index.php index 76eb307..f28dc5e 100644 --- a/app/views/course/lti/index.php +++ b/app/views/course/lti/index.php @@ -11,8 +11,8 @@ <? foreach ($links as $link): ?> <? - $launch_url = $link->deployment->getLaunchURL(); - $unfinished_deep_linking = !empty($link->deployment->options['unfinished_deep_linking']); + $launch_url = $link->getLaunchURL(); + $unfinished_deep_linking = !empty($link->options['unfinished_deep_linking']); $no_consent = !LtiToolPrivacySettings::countBySql( '`tool_id` = :tool_id AND `user_id` = :user_id', ['tool_id' => $link->deployment->tool_id, 'user_id' => $GLOBALS['user']->id] @@ -22,7 +22,7 @@ <article class="studip"> <header> <h1> - <?= htmlReady($link->deployment->title) ?> + <?= htmlReady($link->title) ?> <?= $unfinished_deep_linking ? '(' . _('LTI Deep Linking noch nicht fertig eingerichtet') . ')' : '' ?> </h1> @@ -50,7 +50,7 @@ $show_admin_actions = $GLOBALS['perm']->have_studip_perm('tutor', $link->course_id); if ($show_admin_actions) { $menu->addLink( - $controller->url_for('lti/tool/index/' . $link->course_id . '/' . $link->deployment->tool->id), + $controller->url_for('lti/tool/index/' . $link->course_id . '/' . $link->deployment->tool_id, ['link_id' => $link->id]), _('Konfiguration des LTI-Tools anzeigen'), Icon::create('info-circle'), ['data-dialog' => 'size=default'] @@ -75,7 +75,7 @@ $menu->addLink( sprintf( 'javascript:void(STUDIP.Dialog.confirmAsPost(\'%s\', \'%s\'))', - sprintf(_('Wollen Sie das LTI-Tool "%s" wirklich entfernen?'), $link->deployment->title), + sprintf(_('Wollen Sie das LTI-Tool "%s" wirklich entfernen?'), $link->title), $controller->url_for('lti/tool/delete/' . $link->course_id . '/' . $link->deployment->tool_id) ), _('LTI-Tool entfernen'), @@ -92,11 +92,11 @@ <? if ($unfinished_deep_linking) : ?> <?= Studip\LinkButton::create( _('Einrichtung abschließen'), - $controller->url_for('course/lti/select_link/' . $link->id, ['tool_id' => $link->tool_id]), + $controller->url_for('course/lti/select_link/' . $link->id, ['tool_id' => $link->deployment->tool_id]), ['target' => '_blank'] ) ?> <? elseif ($no_consent) : ?> - <?= formatReady($link->deployment->description) ?> + <?= formatReady($link->description) ?> <p><?= _('Sie haben der Datenweitergabe an das LTI-Tool noch nicht zugestimmt und können es deswegen noch nicht nutzen.') ?></p> <?= Studip\LinkButton::create( _('Datenschutzeinstellungen öffnen'), @@ -105,9 +105,9 @@ ) ?> <? elseif ($launch_url) : ?> <? - $document_target = $link->deployment->options['document_target'] ?? ''; + $document_target = $link->options['document_target'] ?? ''; ?> - <?= formatReady($link->deployment->description) ?> + <?= formatReady($link->description) ?> <? if ($document_target === 'iframe') : ?> <iframe style="border: none; height: 640px; width: 100%;" src="<?= $controller->link_for('course/lti/iframe/' . $link->id) ?>"></iframe> diff --git a/app/views/course/lti/select_link.php b/app/views/course/lti/select_link.php index 370587e..b0a30f6 100644 --- a/app/views/course/lti/select_link.php +++ b/app/views/course/lti/select_link.php @@ -2,13 +2,13 @@ /** * @var AuthenticatedController $controller * @var LtiTool $tool - * @var LtiDeployment $deployment + * @var \LtiResourceLink $link */ ?> <form class="default" method="post" - action="<?= $controller->link_for('course/lti/process_select_link/' . htmlReady($deployment->id), ['tool_id' => $tool->id]) ?>"> + action="<?= $controller->link_for('course/lti/process_select_link/' . htmlReady($link->id ?? ''), ['tool_id' => $tool->id]) ?>"> <?= CSRFProtection::tokenTag() ?> - <?= $this->render_partial('lti/_tool_info', ['tool' => $tool, 'deployment' => $deployment]) ?> + <?= $this->render_partial('lti/_tool_info', ['tool' => $tool, 'deployment' => $link->deployment ?? null]) ?> <div data-dialog-button> <?= \Studip\Button::create(_('Weiter'), 'continue') ?> </div> diff --git a/app/views/course/lti/select_tool.php b/app/views/course/lti/select_tool.php index 118c1ae..d2af241 100644 --- a/app/views/course/lti/select_tool.php +++ b/app/views/course/lti/select_tool.php @@ -14,10 +14,10 @@ <select name="selected_deployment_id"> <? foreach ($global_tool_deployments as $deployment) : ?> <option value="<?= htmlReady($deployment->id) ?>"> - <? if ($deployment->title !== $deployment->tool->name) : ?> - <?= htmlReady(sprintf('%1$s (%2$s)', $deployment->tool->name, $deployment->title)) ?> + <? if ($deployment->name) : ?> + <?= htmlReady(sprintf('%1$s (%2$s)', $deployment->tool->name, $deployment->name)) ?> <? else : ?> - <?= htmlReady($deployment->title) ?> + <?= htmlReady($deployment->tool->name) ?> <? endif ?> </option> <? endforeach ?> diff --git a/app/views/lti/_deployment_user_info.php b/app/views/lti/_link_user_info.php index b43da8f..568aaf8 100644 --- a/app/views/lti/_deployment_user_info.php +++ b/app/views/lti/_link_user_info.php @@ -1,37 +1,37 @@ <? /** - * @var LtiDeployment $deployment + * @var \LtiResourceLink $link */ ?> -<? if (!empty($deployment)) : ?> +<? if (!empty($link)) : ?> <article class="studip"> - <header><h1><?= htmlReady($deployment->title) ?></h1></header> + <header><h1><?= htmlReady($link->title) ?></h1></header> <section> - <? if ($deployment->tool->range_id === 'global') : ?> + <? if ($link->deployment->tool->range_id === 'global') : ?> <p> <?= sprintf( 'Dies ist eine Einbindung des LTI-Tools „%s“.', - htmlReady($deployment->tool->name) + htmlReady($link->deployment->tool->name) ) ?> </p> <? endif ?> - <p><?= formatReady($deployment->description ?? '') ?></p> + <p><?= formatReady($link->description ?? '') ?></p> <? - $url_parts = parse_url($deployment->getLaunchURL()); + $url_parts = parse_url($link->getLaunchURL()); ?> <? if (!empty($url_parts['host'])) : ?> <p><?= _('Domain') ?>: <?= htmlReady($url_parts['host']) ?></p> <? endif ?> - <? if ($deployment->tool->terms_of_use_url || $deployment->tool->privacy_policy_url) : ?> + <? if ($link->deployment->tool->terms_of_use_url || $link->deployment->tool->privacy_policy_url) : ?> <p> - <? if ($deployment->tool->terms_of_use_url) : ?> - <a href="<?= htmlReady($deployment->tool->terms_of_use_url) ?>"> + <? if ($link->deployment->tool->terms_of_use_url) : ?> + <a href="<?= htmlReady($link->deployment->tool->terms_of_use_url) ?>"> <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?> <?= _('Nutzungsbedingungen') ?> </a> <? endif ?> - <? if ($deployment->tool->privacy_policy_url) : ?> - <a href="<?= htmlReady($deployment->tool->privacy_policy_url) ?>"> + <? if ($link->deployment->tool->privacy_policy_url) : ?> + <a href="<?= htmlReady($link->deployment->tool->privacy_policy_url) ?>"> <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?> <?= _('Datenschutzerklärung') ?> </a> diff --git a/app/views/lti/_tool_form_fields.php b/app/views/lti/_tool_form_fields.php index 0dcbd25..d1dccec 100644 --- a/app/views/lti/_tool_form_fields.php +++ b/app/views/lti/_tool_form_fields.php @@ -1,7 +1,7 @@ <?php /** * @var LtiTool $tool - * @var ?LtiDeployment $deployment + * @var ?\LtiResourceLink $link */ ?> <fieldset> @@ -10,11 +10,11 @@ <span class="textlabel"><?= _('Titel') ?></span> <span class="asterisk">*</span> <input type="text" name="name" required - value="<?= htmlReady($tool->name ?? '') ?>"> + value="<?= htmlReady($link->title ?? $tool->name ?? '') ?>"> </label> <label> <?= _('Beschreibung') ?> - <textarea name="description" class="wysiwyg"><?= wysiwygReady($deployment->description ?? '') ?></textarea> + <textarea name="description" class="wysiwyg"><?= wysiwygReady($link->description ?? '') ?></textarea> </label> <label> <?= _('Datenschutzhinweise') ?> @@ -114,8 +114,8 @@ <?= _('Zusätzliche LTI-Parameter') ?> <?= tooltipIcon(_('Ein Wert pro Zeile, Beispiel: Review:Chapter=1.2.56')) ?> <textarea name="custom_parameters"><?= htmlReady( - !empty($deployment->options['custom_parameters']) - ? $deployment->options['custom_parameters'] + !empty($link->options['custom_parameters']) + ? $link->options['custom_parameters'] : $tool->custom_parameters ?? '' ) ?></textarea> </label> @@ -123,7 +123,7 @@ <fieldset> <legend><?= _('Anzeigeeinstellungen') ?></legend> <label> - <input type="checkbox" name="document_target" value="iframe" <?= isset($deployment->options['document_target']) && $deployment->options['document_target'] === 'iframe' ? ' checked' : '' ?>> + <input type="checkbox" name="document_target" value="iframe" <?= isset($link->options['document_target']) && $link->options['document_target'] === 'iframe' ? ' checked' : '' ?>> <?= _('Anzeige im IFRAME auf der Seite') ?> <?= tooltipIcon(_('Normalerweise wird das externe Tool in einem neuen Fenster angezeigt. Aktivieren Sie diese Option, wenn die Anzeige stattdessen in einem IFRAME erfolgen soll.')) ?> </label> diff --git a/app/views/lti/_tool_info.php b/app/views/lti/_tool_info.php index 0fafc9d..fb9657e 100644 --- a/app/views/lti/_tool_info.php +++ b/app/views/lti/_tool_info.php @@ -1,15 +1,15 @@ <?php /** * @var LtiTool $tool - * @var LtiDeployment $deployment - * @var StudipControlle $controller + * @var \LtiResourceLink $link + * @var StudipController $controller */ ?> <? if (!empty($tool)) : ?> <article class="studip"> <header> - <? if ($deployment) : ?> - <h1><?= htmlReady($deployment->title) ?></h1> + <? if ($link) : ?> + <h1><?= htmlReady($link->title) ?></h1> <? else : ?> <h1><?= htmlReady($tool->name) ?></h1> <? endif ?> @@ -17,10 +17,10 @@ <dl> <dt><?= _('Launch-URL') ?></dt> <dd> - <? if ($deployment && $deployment->launch_url) : ?> - <a href="<?= htmlReady($deployment->launch_url) ?>"> + <? if ($link && $link->launch_url) : ?> + <a href="<?= htmlReady($link->launch_url) ?>"> <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?> - <?= htmlReady($deployment->launch_url) ?> + <?= htmlReady($link->launch_url) ?> </a> <? else : ?> <a href="<?= htmlReady($tool->launch_url) ?>"> @@ -54,37 +54,32 @@ <dd><?= htmlReady($tool->id) ?></dd> <? endif ?> - <? if ($deployment) : ?> + <? if (!empty($link->deployment->id)) : ?> <dt><?= _('Deployment-ID') ?></dt> - <dd><?= htmlReady($deployment->id) ?></dd> + <dd><?= htmlReady($link->deployment->id) ?></dd> - <? if ($parameters = $deployment->getCustomParameters()) : ?> + <? if ($parameters = $link->getCustomParameters()) : ?> <dt><?= _('LTI custom parameters') ?></dt> <dd><?= htmlReady($parameters) ?></dd> <? endif ?> <? endif ?> - <dt><?= _('Direktlink zum LTI-Tool') ?></dt> - <dd> - <ul> - <? foreach ($tool->deployments as $deployment) : ?> - <? - $link = LtiResourceLink::findOneByDeployment_id($deployment->id); - ?> - <? if ($link) : ?> - <li> - <a href="<?= $controller->link_for('course/lti/iframe', $link->id) ?>"> - <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?> - <?= $controller->link_for('course/lti/iframe', $link->id) ?> - </a> - </li> - <? endif ?> - <? endforeach ?> - </ul> - </dd> + <? if ($link) : ?> + <dt><?= _('Direktlink zum LTI-Tool') ?></dt> + <dd> + <ul> + <li> + <a href="<?= $controller->link_for('course/lti/iframe', $link->id) ?>"> + <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?> + <?= $controller->link_for('course/lti/iframe', $link->id) ?> + </a> + </li> + </ul> + </dd> + <? endif ?> </dl> </article> <article class="studip"> <header><h1><?= _('Plattform-Konfiguration') ?></h1></header> - <?= $this->render_partial('lti/_platform_data', ['platform' => \Studip\LTI13a\PlatformManager::getPlatformConfiguration()]) ?> + <?= $this->render_partial('lti/_platform_data', ['platform' => \Studip\LTI13a\PlatformManager::getPlatformConfiguration($tool->id)]) ?> </article> <? endif ?> diff --git a/app/views/lti/tool/add.php b/app/views/lti/tool/add.php index fda0849..fd0c223 100644 --- a/app/views/lti/tool/add.php +++ b/app/views/lti/tool/add.php @@ -3,14 +3,14 @@ * @var AuthenticatedController $controller * @var string $range_id * @var LtiTool $tool - * @var LtiDeployment $deployment + * @var ?\LtiResourceLink $link */ ?> <form class="default" method="post" data-dialog="reload-on-close" - action="<?= $controller->link_for('lti/tool/add/' . $range_id . '/' . $tool->id) ?>"> + action="<?= $controller->link_for('lti/tool/add/' . $range_id . '/' . $tool->id, ['link_id' => $link->id ?? '']) ?>"> <?= CSRFProtection::tokenTag() ?> <?= $this->render_partial('lti/_tool_form_fields', [ 'tool' => $tool, - 'deployment' => $deployment, + 'link' => $link ?? null, ]) ?> </form> diff --git a/app/views/lti/tool/edit.php b/app/views/lti/tool/edit.php index 5b29dd3..74f40c3 100644 --- a/app/views/lti/tool/edit.php +++ b/app/views/lti/tool/edit.php @@ -3,16 +3,16 @@ * @var AuthenticatedController $controller * @var string $range_id * @var LtiTool $tool - * @var LtiDeployment $deployment + * @var ?\LtiResourceLink $link */ ?> <? if ($tool) : ?> <form class="default" method="post" data-dialog="reload-on-close" - action="<?= $controller->link_for('lti/tool/edit/' . $range_id . '/' . $tool->id) ?>"> + action="<?= $controller->link_for('lti/tool/edit/' . $range_id . '/' . $tool->id, ['link_id' => $link->id ?? '']) ?>"> <?= CSRFProtection::tokenTag() ?> <?= $this->render_partial('lti/_tool_form_fields', [ 'tool' => $tool, - 'deployment' => $deployment, + 'deployment' => $link ?? null, ]) ?> </form> <? endif ?> diff --git a/db/migrations/6.0.51_add_columns_to_lti_resource_links_table.php b/db/migrations/6.0.51_add_columns_to_lti_resource_links_table.php new file mode 100644 index 0000000..9385908 --- /dev/null +++ b/db/migrations/6.0.51_add_columns_to_lti_resource_links_table.php @@ -0,0 +1,79 @@ +<?php + + +class AddColumnsToLtiResourceLinksTable extends Migration +{ + public function description() + { + return 'Add missing columns to the lti_resource_links table.'; + } + + protected function up() + { + $db = DBManager::get(); + + //Clone the launch_url and options column from lti_deployments: + $db->exec( + "ALTER TABLE `lti_resource_links` + ADD COLUMN `title` VARCHAR(255) NOT NULL DEFAULT '', + ADD COLUMN `description` TEXT NULL, + ADD COLUMN `launch_url` VARCHAR(255) NOT NULL DEFAULT '', + ADD COLUMN `options` TEXT" + ); + + $db->exec( + "UPDATE `lti_resource_links` + JOIN `lti_deployments` + ON `lti_deployments`.`id` = `lti_resource_links`.`deployment_id` + SET + `lti_resource_links`.`title` = `lti_deployments`.`title`, + `lti_resource_links`.`description` = `lti_deployments`.`description`, + `lti_resource_links`.`launch_url` = `lti_deployments`.`launch_url`, + `lti_resource_links`.`options` = `lti_deployments`.`options`" + ); + + $db->exec( + "ALTER TABLE `lti_deployments` + DROP COLUMN `title`, + DROP COLUMN `description`, + DROP COLUMN `launch_url`, + DROP COLUMN `options`, + ADD COLUMN `purpose` ENUM ('general', 'deep_linking') NOT NULL DEFAULT 'general', + ADD COLUMN `name` VARCHAR(255) NOT NULL DEFAULT ''" + ); + } + + protected function down() + { + $db = DBManager::get(); + + $db->exec( + "ALTER TABLE `lti_deployments` + DROP COLUMN `name`, + DROP COLUMN `purpose`, + ADD COLUMN `title` VARCHAR(255) NOT NULL DEFAULT '', + ADD COLUMN `description` TEXT NULL, + ADD COLUMN `launch_url` VARCHAR(255) NOT NULL DEFAULT '', + ADD COLUMN `options` TEXT" + ); + + $db->exec( + "UPDATE `lti_deployments` + JOIN `lti_resource_links` + ON `lti_deployments`.`id` = `lti_resource_links`.`deployment_id` + SET + `lti_deployments`.`title` = `lti_resource_links`.`title`, + `lti_deployments`.`description` = `lti_resource_links`.`description`, + `lti_deployments`.`launch_url` = `lti_resource_links`.`launch_url`, + `lti_deployments`.`options` = `lti_resource_links`.`options`" + ); + + $db->exec( + "ALTER TABLE `lti_resource_links` + DROP COLUMN `title`, + DROP COLUMN `description`, + DROP COLUMN launch_url, + DROP COLUMN `options`" + ); + } +} diff --git a/lib/classes/LTI13a/PlatformManager.php b/lib/classes/LTI13a/PlatformManager.php index 10c7cb1..9017267 100644 --- a/lib/classes/LTI13a/PlatformManager.php +++ b/lib/classes/LTI13a/PlatformManager.php @@ -31,17 +31,20 @@ class PlatformManager * Generates an object containing the settings for using this Stud.IP * as a platform that connects to an LTI tool via Deep Linking. * - * @param string $tool_id An optional LTI tool ID that is used to construct + * @param string $link_id The Stud.IP LTI Resource Link ID that is used to construct * the platform return URL. * + * @param string $course_id An optional Stud.IP course for which to get + * the deep linking configuration. + * * @return DeepLinkingSettings The settings for deep linking. */ - public static function getDeepLinkingConfiguration(string $tool_id = '') : DeepLinkingSettings + public static function getDeepLinkingConfiguration(string $link_id, string $course_id = '') : DeepLinkingSettings { $c = \Config::get(); return new DeepLinkingSettings( - self::getDeepLinkingReturnUrl($tool_id), + self::getDeepLinkingReturnUrl($link_id, $course_id), [LtiResourceLinkInterface::TYPE], ['window', 'iframe'], 'text/html', @@ -85,13 +88,20 @@ class PlatformManager /** * Generates the URL for returning from the tool in an LTI deep linking process. * - * @param string $tool_id The optional LTI Tool-ID to append to the URL. + * @param string $link_id The Stud.IP LTI Resource Link ID to append to the URL. + * + * @param string $course_id An optional Stud.IP course for which to generate + * the deep linking return URL. * * @return string The URL for returning from an LTI deep linking process. */ - public static function getDeepLinkingReturnUrl(string $tool_id = '') : string + public static function getDeepLinkingReturnUrl(string $link_id, string $course_id = '') : string { - return \URLHelper::getURL('dispatch.php/course/lti/save_link/' . $tool_id, null, true); + $params = ['link_id' => $link_id]; + if ($course_id) { + $params['cid'] = $course_id; + } + return \URLHelper::getURL('dispatch.php/course/lti/save_link/' . $link_id, $params, true); } /** diff --git a/lib/classes/LTI13a/Registration.php b/lib/classes/LTI13a/Registration.php index c4b898f..5498ace 100644 --- a/lib/classes/LTI13a/Registration.php +++ b/lib/classes/LTI13a/Registration.php @@ -2,6 +2,7 @@ namespace Studip\LTI13a; +use OAT\Library\Lti1p3Core\Exception\LtiException; use OAT\Library\Lti1p3Core\Registration\RegistrationInterface; use OAT\Library\Lti1p3Core\Tool\ToolInterface; use OAT\Library\Lti1p3Core\Platform\PlatformInterface; @@ -10,7 +11,8 @@ use OAT\Library\Lti1p3Core\Security\Key\KeyChainInterface; class Registration implements RegistrationInterface { public function __construct( - protected ?\LtiTool $tool + protected ?\LtiTool $tool, + protected ?\LtiResourceLink $link = null ) { } @@ -24,13 +26,27 @@ class Registration implements RegistrationInterface return $this->tool; } + public function setLtiResourceLink(\LtiResourceLink $link) + { + $this->link = $link; + } + + public function getLtiResourceLink() : ?\LtiResourceLink + { + return $this->link; + } + #[\Override] public function getIdentifier(): string { if (!$this->tool) { return ''; } - return $this->tool->id; + if ($this->link) { + return $this->tool->id . '_' . $this->link->id; + } else { + return $this->tool->id; + } } #[\Override] @@ -63,7 +79,11 @@ class Registration implements RegistrationInterface if (!$this->tool) { return []; } - return \DBManager::get()->fetchFirst("SELECT `id` FROM `lti_deployments` WHERE `tool_id` = ?", [$this->tool->id]); + if ($this->link) { + return [$this->link->deployment_id]; + } else { + return \DBManager::get()->fetchFirst("SELECT `id` FROM `lti_deployments` WHERE `tool_id` = ?", [$this->tool->id]); + } } #[\Override] @@ -72,10 +92,14 @@ class Registration implements RegistrationInterface if (!$this->tool) { return false; } - return \LtiDeployment::countBySql( - "`tool_id` = :tool_id AND `id` = :deployment_id", - ['tool_id' => $this->tool->id, 'deployment_id' => $deploymentId] - ) > 0; + if ($this->link) { + return $this->link->deployment_id == $deploymentId; + } else { + return \LtiDeployment::countBySql( + "`tool_id` = :tool_id AND `id` = :deployment_id", + ['tool_id' => $this->tool->id, 'deployment_id' => $deploymentId] + ) > 0; + } } #[\Override] @@ -98,12 +122,13 @@ class Registration implements RegistrationInterface #[\Override] public function getToolKeyChain(): ?KeyChainInterface { - if (!$this->tool) { + if (!$this->tool || $this->tool->jwks_url) { return null; } + $keyring = $this->tool->getKeyring(); if (!$keyring) { - $keyring = $this->tool->getKeyring(true); + throw new LtiException('Failed to load public key for tool ' . $this->tool->id); } return $keyring->toKeyChain(); } diff --git a/lib/classes/LTI13a/RegistrationManager.php b/lib/classes/LTI13a/RegistrationManager.php index 922cb58..05bcc2e 100644 --- a/lib/classes/LTI13a/RegistrationManager.php +++ b/lib/classes/LTI13a/RegistrationManager.php @@ -7,15 +7,29 @@ use OAT\Library\Lti1p3Core\Registration\RegistrationInterface; class RegistrationManager implements RegistrationRepositoryInterface { + protected ?\LtiResourceLink $link = null; + + public function setResourceLink(\LtiResourceLink $link) + { + $this->link = $link; + } + #[\Override] public function find(string $identifier): ?RegistrationInterface { //The identifier is the ID of a tool. $tool = \LtiTool::find($identifier); + $link = null; + if (!$tool) { + //Attempt to find the tool and a resource link. + $id_parts = explode('_', $identifier); + $tool = \LtiTool::find($id_parts[0]); + $link = \LtiResourceLink::find($id_parts[1]); + } if (!$tool) { return null; } - return new Registration($tool); + return new Registration($tool, $link); } /** @@ -42,7 +56,7 @@ class RegistrationManager implements RegistrationRepositoryInterface } $tool = \LtiTool::find($clientId); if ($tool) { - return new Registration($tool); + return new Registration($tool, $this->link); } return null; } @@ -51,7 +65,8 @@ class RegistrationManager implements RegistrationRepositoryInterface public function findByPlatformIssuer(string $issuer, string $clientId = null): ?RegistrationInterface { //Only handle requests for registrations of this Stud.IP: - if ($issuer !== \Config::get()->STUDIP_INSTALLATION_ID) { + $platform_config = \Studip\LTI13a\PlatformManager::getPlatformConfiguration(); + if ($issuer !== $platform_config->getAudience()) { //Invalid issuer. return null; } diff --git a/lib/models/LtiDeployment.php b/lib/models/LtiDeployment.php index e59e738..86f18a0 100644 --- a/lib/models/LtiDeployment.php +++ b/lib/models/LtiDeployment.php @@ -12,15 +12,10 @@ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 * * @property int $id database column - * @property string $title database column - * @property string $description database column * @property int $tool_id database column - * @property string $launch_url database column * @property int $mkdate database column * @property int $chdate database column - * @property JSONArrayObject|null $options database column * @property SimpleORMapCollection<LtiGrade> $grades has_many LtiGrade - * @property Course $course belongs_to Course * @property LtiTool $tool belongs_to LtiTool */ @@ -33,8 +28,6 @@ class LtiDeployment extends SimpleORMap { $config['db_table'] = 'lti_deployments'; - $config['serialized_fields']['options'] = JSONArrayObject::class; - $config['belongs_to']['tool'] = [ 'class_name' => LtiTool::class, 'foreign_key' => 'tool_id' @@ -61,6 +54,8 @@ class LtiDeployment extends SimpleORMap /** * Get the launch_url of this entry. + * + * @deprecated */ public function getLaunchURL() { @@ -72,6 +67,8 @@ class LtiDeployment extends SimpleORMap /** * Get the consumer_key of this entry. + * + * @deprecated */ public function getConsumerKey() { @@ -80,6 +77,8 @@ class LtiDeployment extends SimpleORMap /** * Get the consumer_secret of this entry. + * + * @deprecated */ public function getConsumerSecret() { @@ -88,6 +87,8 @@ class LtiDeployment extends SimpleORMap /** * Get the oauth_signature_method of this entry. + * + * @deprecated */ public function getOauthSignatureMethod() { @@ -96,6 +97,8 @@ class LtiDeployment extends SimpleORMap /** * Get the custom_parameters of this entry. + * + * @deprecated */ public function getCustomParameters() { @@ -107,23 +110,6 @@ class LtiDeployment extends SimpleORMap return $parameters; } - public function getCustomLtiParameterArray() : array - { - $parameter_str = $this->getCustomParameters(); - if (empty($parameter_str)) { - return []; - } - $parameters = explode("\n", $parameter_str); - $array = []; - foreach ($parameters as $parameter) { - $key_value_parts = explode('=', $parameter, 2); - if (count($key_value_parts) === 2) { - $array[trim($key_value_parts[0])] = trim($key_value_parts[1]); - } - } - return ['https://purl.imsglobal.org/spec/lti/claim/custom' => $array]; - } - /** * Get the send_lis_person attribute of this entry. */ diff --git a/lib/models/LtiGrade.php b/lib/models/LtiGrade.php index a7a5dbc..b037f30 100644 --- a/lib/models/LtiGrade.php +++ b/lib/models/LtiGrade.php @@ -18,6 +18,9 @@ * @property int $chdate database column * @property LtiDeployment $link belongs_to LtiDeployment * @property User $user belongs_to User + * + * NOTE: LtiGrade is only for the LTI 1.0/1.1 interface. + * The LTI 1.3A interface uses the grade book tables for storing grades. */ class LtiGrade extends SimpleORMap diff --git a/lib/models/LtiResourceLink.php b/lib/models/LtiResourceLink.php index 785c350..0ea15aa 100644 --- a/lib/models/LtiResourceLink.php +++ b/lib/models/LtiResourceLink.php @@ -23,7 +23,11 @@ use OAT\Library\Lti1p3Core\Util\Collection\CollectionInterface; * @property int $id database column * @property int $deployment_id database column * @property string $course_id database column + * @property string $title database column + * @property string $description database column * @property int $position database column + * @property string $launch_url database column + * @property JSONArrayObject|null $options database column * @property int $mkdate database column * @property int $chdate database column * @property ?LtiDeployment $deployment related object @@ -35,6 +39,8 @@ class LtiResourceLink extends \SimpleORMap implements LtiResourceLinkInterface { $config['db_table'] = 'lti_resource_links'; + $config['serialized_fields']['options'] = JSONArrayObject::class; + $config['belongs_to']['course'] = [ 'class_name' => Course::class, 'foreign_key' => 'course_id' @@ -89,14 +95,19 @@ class LtiResourceLink extends \SimpleORMap implements LtiResourceLinkInterface return self::findOneBySQL('course_id = ? AND position = ?', [$course_id, $position]); } + public function getLaunchURL() + { + if (!empty($this->deployment->tool) && empty($this->deployment->tool->allow_custom_url) && empty($this->deployment->tool->deep_linking) || empty($this->launch_url)) { + return $this->deployment->tool->launch_url; + } + return $this->launch_url; + } + //OAT library LtiResourceLinkInterface and ResourceInterface implementation: public function getUrl(): ?string { - if ($this->deployment) { - return $this->deployment->getLaunchURL(); - } - return null; + return $this->getLaunchURL(); } public function getIcon(): ?array @@ -154,10 +165,7 @@ class LtiResourceLink extends \SimpleORMap implements LtiResourceLinkInterface public function getTitle(): ?string { - if ($this->deployment) { - return $this->deployment->title; - } - return null; + return $this->title ?? $this->deployment->tool->name ?? null; } public function getText(): ?string @@ -184,4 +192,31 @@ class LtiResourceLink extends \SimpleORMap implements LtiResourceLinkInterface ) ); } + + public function getCustomParameters() + { + $parameters = ''; + if (!empty($this->deployment->tool)) { + $parameters = $this->deployment->tool->custom_parameters; + } + $parameters .= $this->options['custom_parameters'] ?? ''; + return $parameters; + } + + public function getCustomLtiParameterArray() : array + { + $parameter_str = $this->getCustomParameters(); + if (empty($parameter_str)) { + return []; + } + $parameters = explode("\n", $parameter_str); + $array = []; + foreach ($parameters as $parameter) { + $key_value_parts = explode('=', $parameter, 2); + if (count($key_value_parts) === 2) { + $array[trim($key_value_parts[0])] = trim($key_value_parts[1]); + } + } + return ['https://purl.imsglobal.org/spec/lti/claim/custom' => $array]; + } } |
