aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRasmus Fuhse <fuhse@data-quest.de>2025-07-04 11:26:38 +0000
committerRasmus Fuhse <fuhse@data-quest.de>2025-07-04 11:26:38 +0000
commitf1613a24917bf32de90f042801d72b9fc18e43a2 (patch)
tree8d298c4deb1b12e3a5104b311a144d21a57beb60
parent64a29f7621f459a444835ea85be204f7d12ed302 (diff)
course/lti/process_select_link: redirect on error with deployments, fixes #5488cherry-pick-d98e8f81
Closes #5488 Merge request studip/studip!4183 (cherry picked from commit d98e8f811ee969fc94f922a70ad270ff02d057fc) 6e3310be course/lti/process_select_link: redirect on error with deployments b1f72a9f began rewriting course/lti/select_link and course/lti/process_select_link actions 74f13cb3 use LtiResourceLink instead of LtiDeployment 8c9e7707 course/lti/process_select_link: made lti resource link ID mandatory 5ee5de11 Revert "course/lti/process_select_link: made lti resource link ID mandatory" c26e2605 Revert "use LtiResourceLink instead of LtiDeployment" d466a82c Revert "began rewriting course/lti/select_link and course/lti/process_select_link actions" ec9ebdd9 course/lti/process_select_link action: create LtiResourceLink objects bb8aadb6 fixed typos 7c399153 fixed errors c3d046b3 added redirects 6be413d1 Revert "added redirects" 3d4462f7 Revert "fixed errors" adb61667 Revert "fixed typos" fcd16406 Revert "course/lti/process_select_link action: create LtiResourceLink objects" 1f34f17d began rewriting course/lti/select_link and course/lti/process_select_link actions 5411e842 use LtiResourceLink instead of LtiDeployment 7d3b22fb course/lti/process_select_link: made lti resource link ID mandatory a92bec66 added migration 6f1d77ec changed code a08519ee changed more code, fixed errors ecfab68d extended migration 4b441ca9 extended migration 18f6a0bb added columns to lti_deployments table, rewrote code for new database structure 40776ead replaced attribute access of LtiDeployment with LtiResourceLink 1150e772 continued moving attribute access 28ac0808 fixed course/lti/select_link action a9189788 fixed lti/tool/index for tools in courses 14e29ec7 fixed errors d1cf3aae fixed errors, added warning for more than one general LTI deployment per tool 46f0ea9a added deep link count to admin/lti/index dc8058f3 removed unused code d6d1491d fixed errors a7f34412 removed debug code 944782dd fixed errors 7ee7716d fixed more errors 35563dc9 set course-ID as URL parameter for deep linking return URL a1af83e5 fixed error f2bd12cc course/lti/save_link: attempted to fix "no registration platform side" error 041d65de fixed error d77844e0 fixed errors 581630b5 added debug code, allow setting link in registration manager c9174799 removed debug code b39ddf23 test 1f99d252 removed extra claim 75a25a15 made LTI requests have the same registration 680e9945 Prioritize JWKS URL over static key chain 46f3f6f4 Return LTi exception on public key failure 9d97dbbf Make public key unsetable Co-authored-by: Moritz Strohm <strohm@data-quest.de>
-rw-r--r--app/controllers/course/lti.php264
-rw-r--r--app/controllers/lti/tool.php62
-rw-r--r--app/views/admin/lti/index.php22
-rw-r--r--app/views/course/lti/add_link.php2
-rw-r--r--app/views/course/lti/consent.php2
-rw-r--r--app/views/course/lti/grades.php2
-rw-r--r--app/views/course/lti/grades_user.php2
-rw-r--r--app/views/course/lti/index.php18
-rw-r--r--app/views/course/lti/select_link.php6
-rw-r--r--app/views/course/lti/select_tool.php6
-rw-r--r--app/views/lti/_link_user_info.php (renamed from app/views/lti/_deployment_user_info.php)24
-rw-r--r--app/views/lti/_tool_form_fields.php12
-rw-r--r--app/views/lti/_tool_info.php53
-rw-r--r--app/views/lti/tool/add.php6
-rw-r--r--app/views/lti/tool/edit.php6
-rw-r--r--db/migrations/6.0.51_add_columns_to_lti_resource_links_table.php79
-rw-r--r--lib/classes/LTI13a/PlatformManager.php22
-rw-r--r--lib/classes/LTI13a/Registration.php43
-rw-r--r--lib/classes/LTI13a/RegistrationManager.php21
-rw-r--r--lib/models/LtiDeployment.php34
-rw-r--r--lib/models/LtiGrade.php3
-rw-r--r--lib/models/LtiResourceLink.php51
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: ?>
&ndash;
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: ?>
&ndash;
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];
+ }
}