From 28307fdb26320d5b0331f9c031f7ba078acab578 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani Date: Fri, 19 Dec 2025 16:26:50 +0100 Subject: Redeveloped LTI Resource links --- app/controllers/admin/lti/AdminBaseController.php | 49 ++++++--- app/controllers/admin/lti/registrations.php | 34 ++++-- app/controllers/admin/lti/resources.php | 122 +++++++++++++++++++++ app/controllers/course/lti.php | 55 ++++++---- app/controllers/lti/ags.php | 14 --- app/controllers/lti/auth.php | 21 +--- lib/classes/LTI13a/LineItemRepository.php | 4 +- lib/classes/LTI13a/RegistrationManager.php | 26 ++++- lib/models/Lti/Registration.php | 18 +-- lib/models/Lti/ResourceLink.php | 18 ++- lib/modules/LtiToolModule.php | 4 +- resources/assets/stylesheets/scss/lti.scss | 51 +++++++++ resources/assets/stylesheets/scss/select.scss | 12 ++ resources/assets/stylesheets/studip.scss | 51 +++++++++ resources/vue/apps/lti/deployments/Create.vue | 11 +- resources/vue/apps/lti/deployments/Index.vue | 7 +- resources/vue/apps/lti/registrations/Create.vue | 11 +- resources/vue/apps/lti/registrations/Edit.vue | 12 +- resources/vue/apps/lti/registrations/Index.vue | 32 ++++-- resources/vue/apps/lti/registrations/Show.vue | 3 +- resources/vue/apps/lti/resources/Create.vue | 18 +++ resources/vue/apps/lti/resources/Edit.vue | 22 ++++ resources/vue/apps/lti/resources/Index.vue | 107 ++++++++++++++++++ resources/vue/components/StudipSelect.vue | 5 + .../components/lti/deployments/DeploymentIndex.vue | 17 ++- .../lti/deployments/LtiDeploymentForm.vue | 16 ++- resources/vue/components/lti/helpers/urls.js | 31 ++++++ .../lti/registrations/LtiRegistrationForm.vue | 18 ++- .../vue/components/lti/resources/ResourceForm.vue | 114 +++++++++++++++++++ .../vue/components/lti/resources/ToolCard.vue | 104 ++++++++++++++++++ 30 files changed, 848 insertions(+), 159 deletions(-) create mode 100644 app/controllers/admin/lti/resources.php create mode 100644 resources/vue/apps/lti/resources/Create.vue create mode 100644 resources/vue/apps/lti/resources/Edit.vue create mode 100644 resources/vue/apps/lti/resources/Index.vue create mode 100644 resources/vue/components/lti/helpers/urls.js create mode 100644 resources/vue/components/lti/resources/ResourceForm.vue create mode 100644 resources/vue/components/lti/resources/ToolCard.vue diff --git a/app/controllers/admin/lti/AdminBaseController.php b/app/controllers/admin/lti/AdminBaseController.php index 7b75646..4022606 100644 --- a/app/controllers/admin/lti/AdminBaseController.php +++ b/app/controllers/admin/lti/AdminBaseController.php @@ -2,6 +2,8 @@ namespace LTI; use AuthenticatedController; +use Config; +use Context; use Icon; use Navigation; use PageLayout; @@ -17,10 +19,19 @@ abstract class AdminBaseController extends AuthenticatedController parent::before_filter($action, $args); $GLOBALS['perm']->check('root'); - Navigation::activateItem('/admin/config/lti'); + $this->range_id = Context::getId(); + + if ($this->range_id) { + Navigation::activateItem('/course/lti/registrations'); + } else { + Navigation::activateItem('/admin/config/lti'); + } + PageLayout::setTitle(_('LTI-Registrierungen')); $this->role = Request::get('role', 'tool'); + $this->isToolSharingEnabled = Config::get()->ENABLE_SHARING_COURSES_AS_LTI_TOOLS; + $this->buildSidebar(); } @@ -34,10 +45,12 @@ abstract class AdminBaseController extends AuthenticatedController $this->url_for('admin/lti/registrations', ['role' => 'tool']), )->setActive($this->role !== 'platform'); - $viewWidget->addLink( - _('LTI-Platforms'), - $this->url_for('admin/lti/registrations', ['role' => 'platform']) - )->setActive($this->role === 'platform'); + if ($this->isToolSharingEnabled) { + $viewWidget->addLink( + _('LTI-Platforms'), + $this->url_for('admin/lti/registrations', ['role' => 'platform']) + )->setActive($this->role === 'platform'); + } Sidebar::Get()->addWidget($viewWidget); @@ -47,13 +60,15 @@ abstract class AdminBaseController extends AuthenticatedController _('Neues LTI-Tool registrieren'), $this->url_for('admin/lti/registrations/create', ['role' => 'tool']), Icon::create('add') - )->asDialog('width=900;height=650'); + )->asDialog('width=900;height=700'); - $actions->addLink( - _('Neues LTI-Platform registrieren'), - $this->url_for('admin/lti/registrations/create', ['role' => 'platform']), - Icon::create('add') - )->asDialog('width=900;height=650'); + if ($this->isToolSharingEnabled) { + $actions->addLink( + _('Neues LTI-Platform registrieren'), + $this->url_for('admin/lti/registrations/create', ['role' => 'platform']), + Icon::create('add') + )->asDialog('width=900;height=700'); + } $actions->addLink( _('Daten zur LTI-Plattform anzeigen'), @@ -61,11 +76,13 @@ abstract class AdminBaseController extends AuthenticatedController Icon::create('info') )->asDialog('width=900;height=700'); - $actions->addLink( - _('Daten zur LTI-Tool anzeigen'), - $this->url_for('admin/lti/registrations/tool_data'), - Icon::create('info') - )->asDialog('width=900;height=700'); + if ($this->isToolSharingEnabled) { + $actions->addLink( + _('Daten zur LTI-Tool anzeigen'), + $this->url_for('admin/lti/registrations/tool_data'), + Icon::create('info') + )->asDialog('width=900;height=700'); + } Sidebar::get()->addWidget($actions); } diff --git a/app/controllers/admin/lti/registrations.php b/app/controllers/admin/lti/registrations.php index c58ae43..c22b793 100644 --- a/app/controllers/admin/lti/registrations.php +++ b/app/controllers/admin/lti/registrations.php @@ -12,12 +12,24 @@ class Admin_Lti_RegistrationsController extends AdminBaseController { public function index_action(): void { - $registrations = Registration::findBySQL( - "`role`= :role ORDER BY `mkdate`, `name`", + $sqlQuery = [ + "`role`= :role AND `range_id` IN (:range_ids) ORDER BY `mkdate`, `name`", [ - 'role' => $this->role + 'role' => $this->role, + 'range_ids' => [$this->range_id, 'global'] ] - ); + ]; + + if ($GLOBALS['perm']->have_perm('root')) { + $sqlQuery = [ + "`role`= :role ORDER BY `mkdate`, `name`", + [ + 'role' => $this->role + ] + ]; + } + + $registrations = Registration::findBySQL(...$sqlQuery); $this->render_vue_app( Studip\VueApp::create('lti/registrations/Index') @@ -63,7 +75,7 @@ class Admin_Lti_RegistrationsController extends AdminBaseController 'name' => Request::get('name'), 'description' => Request::get('description'), 'state' => Request::bool('state', true), - 'range_id' => Context::getId() ?? 'global' + 'range_id' => $this->range_id ?? 'global' ]); $this->storeRegistrationConfigs($registration->id); @@ -185,10 +197,6 @@ class Admin_Lti_RegistrationsController extends AdminBaseController { $common = [ [ - 'name' => 'issuer', - 'value' => Request::get('issuer') - ], - [ 'name' => 'terms_of_use_url', 'value' => Request::get('terms_of_use_url') ], @@ -205,6 +213,10 @@ class Admin_Lti_RegistrationsController extends AdminBaseController $toolCommon = [ ...$common, [ + 'name' => 'audience', + 'value' => Request::get('audience') + ], + [ 'name' => 'launch_url', 'value' => Request::get('launch_url') ], @@ -261,6 +273,10 @@ class Admin_Lti_RegistrationsController extends AdminBaseController return [ ...$common, [ + 'name' => 'issuer', + 'value' => Request::get('issuer') + ], + [ 'name' => 'auth_login_url', 'value' => Request::get('auth_login_url') ], diff --git a/app/controllers/admin/lti/resources.php b/app/controllers/admin/lti/resources.php new file mode 100644 index 0000000..dc3bbba --- /dev/null +++ b/app/controllers/admin/lti/resources.php @@ -0,0 +1,122 @@ +render_vue_app( + Studip\VueApp::create('lti/resources/Create') + ->withProps([ + 'registrations' => $this->getTransformedRegistrations() + ]) + ); + } + + public function store_action(): void + { + CSRFProtection::verifyUnsafeRequest(); + + $registration = Registration::find(Request::get('registration_id')); + + $deployment = Deployment::create([ + 'name' => Request::get('title', $registration->name), + 'registration_id' => $registration->id, + 'deployment_id' => bin2hex(random_bytes(6)), + 'client_id' => $registration->getDefaultDeployment()->client_id + ]); + + $resourceLink = ResourceLink::create([ + 'deployment_id' => $deployment->id, + 'course_id' => $this->range_id, + 'title' => Request::get('title'), + 'description' => Request::get('description'), + 'custom_parameters' => Request::get('custom_parameters'), +// 'color' => Request::get('color'), +// 'icon' => Request::get('icon') + ]); + + PageLayout::postSuccess( + sprintf( + _('Der LTI-Ressource „%s“ wurde hinzugefügt.'), + htmlReady($resourceLink->title) + ) + ); + + $this->redirect('course/lti'); + } + + public function edit_action(ResourceLink $resourceLink): void + { + PageLayout::setTitle(_('LTI-Ressource bearbeiten')); + + $this->render_vue_app( + Studip\VueApp::create('lti/resources/Edit') + ->withProps([ + 'resource' => $resourceLink->transformData(['registration']), + 'registrations' => $this->getTransformedRegistrations() + ]) + ); + } + + public function update_action(ResourceLink $resourceLink): void + { + CSRFProtection::verifyUnsafeRequest(); + + $resourceLink->setData([ + 'title' => Request::get('title'), + 'description' => Request::get('description'), + 'custom_parameters' => Request::get('custom_parameters'), +// 'color' => Request::get('color'), +// 'icon' => Request::get('icon') + ]); + + $resourceLink->store(); + + PageLayout::postSuccess( + sprintf( + _('Der LTI-Ressource „%s“ wurde gespeichert.'), + htmlReady($resourceLink->title) + ) + ); + + $this->redirect('course/lti'); + } + + public function delete_action(ResourceLink $resourceLink): void + { + CSRFProtection::verifyUnsafeRequest(); + + $resourceTitle = $resourceLink->title; + $resourceLink->delete(); + + PageLayout::postSuccess( + sprintf( + _('Der LTI-Ressource „%s“ wurde gelöscht.'), + htmlReady($resourceTitle) + ) + ); + + $this->redirect('course/lti'); + } + + private function getTransformedRegistrations(): array + { + $registrations = Registration::findBySQL( + "`role`= 'tool' AND `state` = 1 AND `range_id` IN (:range_ids) ORDER BY `mkdate`, `name`", + [ + 'range_ids' => [$this->range_id, 'global'] + ] + ); + + return array_map(fn ($r) => $r->transformData(), $registrations); + } + +} diff --git a/app/controllers/course/lti.php b/app/controllers/course/lti.php index 567bce4..331fba0 100644 --- a/app/controllers/course/lti.php +++ b/app/controllers/course/lti.php @@ -3,6 +3,7 @@ use Lti\Deployment; use Lti\Grade; use Lti\Registration; +use Lti\ResourceLink; use OAT\Library\Lti1p3Core\Message\Launch\Builder\LtiResourceLinkLaunchRequestBuilder; use OAT\Library\Lti1p3Core\Message\Launch\Validator\Platform\PlatformLaunchValidator; use OAT\Library\Lti1p3Core\Message\Payload\Claim\AgsClaim; @@ -13,7 +14,6 @@ use OAT\Library\Lti1p3Core\Security\Nonce\NonceRepository; use OAT\Library\Lti1p3DeepLinking\Factory\ResourceCollectionFactory; use OAT\Library\Lti1p3DeepLinking\Message\Launch\Builder\DeepLinkingLaunchRequestBuilder; use Studip\LTI13a\PlatformManager; -use OAT\Library\Lti1p3Core\Message\Payload\MessagePayloadInterface\MessagePayloadInterface; use Studip\LTI13a\RegistrationManager; use Studip\LTI13a\RegistrationRepository; use Studip\OAuth2\NegotiatesWithPsr7; @@ -82,6 +82,8 @@ class Course_LtiController extends StudipController */ public function index_action() { + Helpbar::get()->addPlainText('', _('Auf dieser Seite können Sie externe Anwendungen einbinden, sofern diese den LTI-Standard (Version 1.x order 1.3a) unterstützen.')); + $this->links = []; if ($this->edit_perm) { $this->links = LtiResourceLink::findByCourse_id($this->range_id, 'ORDER BY `position`'); @@ -104,14 +106,12 @@ class Course_LtiController extends StudipController $this->url_for('course/lti/config'), Icon::create('admin') )->asDialog('size=auto'); - $global_tools_available = Registration::countBySQL("`range_id` = 'global' AND `role`='tool'") > 0; - if (Config::get()->LTI_ALLOW_TOOL_CONFIG_IN_COURSE || $global_tools_available) { - $widget->addLink( - _('LTI-Tool hinzufügen'), - $this->url_for('course/lti/select_tool'), - Icon::create('add') - )->asDialog(); - } + + $widget->addLink( + _('LTI-Ressource hinzufügen'), + $this->url_for('admin/lti/resources/create'), + Icon::create('add') + )->asDialog('width=700'); // TODO:: deep_linking` = 1 AND $global_deep_linking_tools_exist = Registration::countBySQL("`range_id` = 'global' AND `role`='tool'") > 0; @@ -124,27 +124,43 @@ 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.')); - //Check for error messages: if (Request::get('deployment_id') && (Request::submitted('lti_msg') || Request::submitted('lti_errormsg'))) { - $deployment = Deployment::find(Request::get('deployment_id')); + $deployment = Deployment::findOneBySQL("deployment_id = ?", [Request::get('deployment_id')]); if ($deployment) { //Get the resource link for the deployment and display the messages: - $link = \LtiResourceLink::findOneBySQL( + $resourceLink = ResourceLink::findOneBySQL( "`deployment_id` = :deployment_id AND `course_id` = :course_id", - ['deployment_id' => $deployment->id, 'course_id' => $this->range_id] + [ + 'deployment_id' => $deployment->id, + 'course_id' => $this->range_id + ] ); - if ($link) { + + if ($resourceLink) { if (Request::get('lti_msg')) { - PageLayout::postInfo(htmlReady($link->title . ': ' . Request::get('lti_msg'))); + PageLayout::postInfo(htmlReady($resourceLink->title . ': ' . Request::get('lti_msg'))); } if (Request::get('lti_errormsg')) { - PageLayout::postError(htmlReady($link->title . ': ' . Request::get('lti_errormsg'))); + PageLayout::postError(htmlReady($resourceLink->title . ': ' . Request::get('lti_errormsg'))); } } } } + + $resources = ResourceLink::findBySQL( + "course_id = :course_id ORDER BY `position`", + [ + 'course_id' => $this->range_id + ] + ); + + $this->render_vue_app( + Studip\VueApp::create('lti/resources/Index') + ->withProps([ + 'resources' => array_map(fn ($r) => $r->transformData(['registration']), $resources) + ]) + ); } public function select_tool_action() @@ -194,7 +210,7 @@ class Course_LtiController extends StudipController return; } //Link the tool in the course: - $link = new \LtiResourceLink(); + $link = new LtiResourceLink(); $link->deployment_id = $selected_deployment->id; $link->course_id = $this->course->id; if ($link->store()) { @@ -211,8 +227,7 @@ class Course_LtiController extends StudipController public function consent_action(string $link_id) { - - $this->resource_link = \LtiResourceLink::find($link_id); + $this->resource_link = LtiResourceLink::find($link_id); if (!$this->resource_link) { PageLayout::postError(_('Die Einbindung eines LTI-Tools ist ungültig.')); return; diff --git a/app/controllers/lti/ags.php b/app/controllers/lti/ags.php index 634f32e..54a91bd 100644 --- a/app/controllers/lti/ags.php +++ b/app/controllers/lti/ags.php @@ -13,20 +13,6 @@ use Studip\LTI13a\RegistrationManager; use Studip\OAuth2\NegotiatesWithPsr7; use Trails\Dispatcher; -/** - * ags.php - LTI assignment and grade services controller - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Moritz Strohm - * @author Murtaza Sultani - * @date 2024 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ class Lti_AgsController extends StudipController { use NegotiatesWithPsr7; diff --git a/app/controllers/lti/auth.php b/app/controllers/lti/auth.php index bc542fd..adfaa4a 100644 --- a/app/controllers/lti/auth.php +++ b/app/controllers/lti/auth.php @@ -1,5 +1,4 @@ allow_nobody = false; $action = basename(get_route()); - if (in_array($action, ['jwks', 'oauth2_token'])) { + if (in_array($action, ['jwks', 'token'])) { $this->allow_nobody = true; $this->with_session = $action !== 'jwks'; } @@ -196,7 +181,7 @@ class Lti_AuthController extends StudipController /** * Generates OAuth2 tokens for LTI tools. */ - public function oauth2_token_action(): void + public function token_action(): void { $platformEncryptionKey = PlatformManager::getPrivateKey()->getContent(); $responseGenerator = new AccessTokenResponseGenerator( @@ -218,7 +203,7 @@ class Lti_AuthController extends StudipController $response = $responseGenerator->generate( $this->getPsrRequest(), $this->getPsrResponse(), - 'lti13a_platform' + '1' ); $this->renderPsrResponse($response); diff --git a/lib/classes/LTI13a/LineItemRepository.php b/lib/classes/LTI13a/LineItemRepository.php index 6e294b8..e25a8b6 100644 --- a/lib/classes/LTI13a/LineItemRepository.php +++ b/lib/classes/LTI13a/LineItemRepository.php @@ -20,7 +20,7 @@ class LineItemRepository implements LineItemRepositoryInterface * @param string $deploymentId The Stud.IP LTI deployment ID. * @return string The corresponding tool name used in the Stud.IP grading context. */ - public static function getGradingToolName(string $toolId, string $deploymentId) : string + public static function getGradingToolName(string $toolId, string $deploymentId): string { return sprintf('lti-%s-%s', $toolId, $deploymentId); } @@ -37,7 +37,7 @@ class LineItemRepository implements LineItemRepositoryInterface * 'course_id' => The Stud.IP course-ID. * In case the search parameters cannot be generated, an empty array is returned. */ - public static function getSearchParametersFromLineItemIdentifier(string $lineItemIdentifier) : array + public static function getSearchParametersFromLineItemIdentifier(string $lineItemIdentifier): array { //$lineItemIdentifier contains the full URL to the line item. //We must extract the course-ID, tool-ID and deployment-ID diff --git a/lib/classes/LTI13a/RegistrationManager.php b/lib/classes/LTI13a/RegistrationManager.php index 2a7ca76..c16bf83 100644 --- a/lib/classes/LTI13a/RegistrationManager.php +++ b/lib/classes/LTI13a/RegistrationManager.php @@ -34,7 +34,18 @@ class RegistrationManager implements RegistrationRepositoryInterface return null; } - return Registration::findByDeploymentIdAndIssuer($deployment->id, $issuer)?->toLti1p3Registration($deployment); + return Registration::findOneBySQL( + "JOIN `lti_registration_configs` `configs` ON (`lti_registrations`.`id` = `configs`.`registration_id`) + JOIN `lti_deployments` `deployments` ON (`lti_registrations`.`id` = `deployments`.`registration_id`) + WHERE `lti_registrations`.`role` = 'platform' + AND `configs`.`name` = 'issuer' + AND `configs`.`value` = :issuer + AND `deployments`.`id` = :deployment_id", + [ + 'issuer' => $issuer, + 'deployment_id' => $deployment->id + ] + )?->toLti1p3Registration($deployment); } public function findByToolIssuer(string $issuer, string $clientId = null): ?RegistrationInterface @@ -45,6 +56,17 @@ class RegistrationManager implements RegistrationRepositoryInterface return null; } - return Registration::findByDeploymentIdAndIssuer($deployment->id, $issuer)?->toLti1p3Registration($deployment); + return Registration::findOneBySQL( + "JOIN `lti_registration_configs` `configs` ON (`lti_registrations`.`id` = `configs`.`registration_id`) + JOIN `lti_deployments` `deployments` ON (`lti_registrations`.`id` = `deployments`.`registration_id`) + WHERE `lti_registrations`.`role` = 'tool' + AND `configs`.`name` = 'audience' + AND `configs`.`value` = :audience + AND `deployments`.`id` = :deployment_id", + [ + 'audience' => $issuer, + 'deployment_id' => $deployment->id + ] + )?->toLti1p3Registration($deployment); } } diff --git a/lib/models/Lti/Registration.php b/lib/models/Lti/Registration.php index 71d8ee0..50a01a6 100644 --- a/lib/models/Lti/Registration.php +++ b/lib/models/Lti/Registration.php @@ -69,7 +69,7 @@ class Registration extends SimpleORMap public function getDefaultDeployment(): ?Deployment { - return Deployment::findOneBySQL("registration_id = ? ORDER BY mkdate", [$this->id]); + return Deployment::findOneBySQL("registration_id = ? ORDER BY id", [$this->id]); } public function getKeyring(): ?Keyring @@ -122,20 +122,4 @@ class Registration extends SimpleORMap { return static::findBySQL("TRUE"); } - - public static function findByDeploymentIdAndIssuer(int $deploymentId, string $issuer): ?self - { - return self::findOneBySQL( - "JOIN `lti_registration_configs` configs ON (`lti_registrations`.`id` = `configs`.`registration_id`) - JOIN `lti_deployments` deployments ON (`lti_registrations`.`id` = `deployments`.`registration_id`) - WHERE `lti_registrations`.`role` = 'platform' - AND `configs`.`name` = 'issuer' - AND `configs`.`value` = :issuer - AND `deployments`.`id` = :deployment_id", - [ - 'issuer' => $issuer, - 'deployment_id' => $deploymentId - ] - ); - } } diff --git a/lib/models/Lti/ResourceLink.php b/lib/models/Lti/ResourceLink.php index a64abcf..8845a94 100644 --- a/lib/models/Lti/ResourceLink.php +++ b/lib/models/Lti/ResourceLink.php @@ -55,8 +55,22 @@ class ResourceLink extends SimpleORMap return $result; } - public static function findByCourseAndPosition($course_id, $position) + public function transformData($with = []): array { - return self::findOneBySQL('course_id = ? AND position = ?', [$course_id, $position]); + $base = [ + ...$this->toRawArray(), + 'chdate' => date('c', $this->chdate), + 'mkdate' => date('c', $this->mkdate) + ]; + + if (in_array('deployment', $with)) { + $base['deployment'] = $this->deployment->transformData(); + } + + if (in_array('registration', $with)) { + $base['registration'] = Registration::find($this->deployment->registration_id)->transformData(); + } + + return $base; } } diff --git a/lib/modules/LtiToolModule.php b/lib/modules/LtiToolModule.php index 9d09d06..e7e6e83 100644 --- a/lib/modules/LtiToolModule.php +++ b/lib/modules/LtiToolModule.php @@ -70,12 +70,14 @@ class LtiToolModule extends CorePlugin implements StudipModule, SystemPlugin, Pr $navigation = new Navigation(_('LTI')); $navigation->setImage(Icon::create('link-extern', Icon::ROLE_INFO_ALT)); $navigation->setActiveImage(Icon::create('link-extern', Icon::ROLE_INFO)); - $navigation->addSubNavigation('index', new Navigation(_('LTI-Tools'), 'dispatch.php/course/lti')); + $navigation->addSubNavigation('index', new Navigation(_('LTI-Ressourcen'), 'dispatch.php/course/lti')); if ($grades) { $navigation->addSubNavigation('grades', new Navigation(_('Ergebnisse'), 'dispatch.php/course/lti/grades')); } + $navigation->addSubNavigation('registrations', new Navigation(_('LTI-Registrierungen'), 'dispatch.php/admin/lti/registrations')); + return ['lti' => $navigation]; } diff --git a/resources/assets/stylesheets/scss/lti.scss b/resources/assets/stylesheets/scss/lti.scss index 58e053b..52be336 100644 --- a/resources/assets/stylesheets/scss/lti.scss +++ b/resources/assets/stylesheets/scss/lti.scss @@ -76,4 +76,55 @@ gap: 10px; } } + + .select-input-group { + margin-bottom: 10px; + + label { + margin-bottom: 0 !important; + } + } + + .tools-card-container { + padding: 0; + list-style-type: none; + display: grid; + grid-gap: 15px; + grid-template-columns: repeat(auto-fit, minmax(250px, 400px)); + + .studip-card { + color: unset; + text-decoration: none; + + &__title { + color: var(--color--content-link); + transition: color var(--transition-duration); + } + + &:hover { + .studip-card__title { + color: var(--color--content-link-hover); + text-decoration: underline; + } + } + + &--create-tool { + display: flex; + align-items: center; + justify-content: center; + border-style: dashed; + background: transparent; + + &:hover { + background-color: var(--color--tile-background); + } + } + } + } + + .use-utility-classes { + .mb-10 { + margin-bottom: 10px; + } + } } diff --git a/resources/assets/stylesheets/scss/select.scss b/resources/assets/stylesheets/scss/select.scss index 93c6a55..8f4229f 100644 --- a/resources/assets/stylesheets/scss/select.scss +++ b/resources/assets/stylesheets/scss/select.scss @@ -91,6 +91,18 @@ form.default .studip-v-select .vs__selected { &__open-indicator { color: var(--color--brand-primary); } + + &__search { + padding: 0 4px !important; + + &::placeholder { + color: var(--dark-gray-color-60) !important; + } + + &:focus { + font-size: unset !important; + } + } } .multiselect__tags { diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss index 1332fba..5cf1a29 100644 --- a/resources/assets/stylesheets/studip.scss +++ b/resources/assets/stylesheets/studip.scss @@ -1033,3 +1033,54 @@ table { } } } + + +.studip-card { + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 10px; + padding: 10px; + transition: var(--transition-duration); + background-color: var(--color--tile-background); + border: 1px solid var(--color--divider); + min-width: 200px; + min-height: 150px; + height: 100%; + + &:hover { + background-color: var(--color--tile-background-hover); + } + + &__title { + margin: 0; + font-size: 16px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + &__header, + &__footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 5px; + } + + &__body { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 5px; + } + + &__description { + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + overflow: hidden; + } +} diff --git a/resources/vue/apps/lti/deployments/Create.vue b/resources/vue/apps/lti/deployments/Create.vue index 3f787c4..cedb27e 100644 --- a/resources/vue/apps/lti/deployments/Create.vue +++ b/resources/vue/apps/lti/deployments/Create.vue @@ -19,16 +19,7 @@ defineProps({ diff --git a/resources/vue/apps/lti/deployments/Index.vue b/resources/vue/apps/lti/deployments/Index.vue index e07bf6a..124b486 100644 --- a/resources/vue/apps/lti/deployments/Index.vue +++ b/resources/vue/apps/lti/deployments/Index.vue @@ -2,6 +2,7 @@ import {$gettext} from "../../../../assets/javascripts/lib/gettext"; import StudipIcon from "../../../components/StudipIcon.vue"; import DeploymentIndex from "../../../components/lti/deployments/DeploymentIndex.vue"; +import {addDeploymentURL, showRegistrationURL} from "../../../components/lti/helpers/urls"; const props = defineProps({ deployments: { @@ -14,9 +15,7 @@ const props = defineProps({ } }); -const addDeployment = () => STUDIP.Dialog.fromURL(STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/deployments/create?registration_id=${props.registration.id}`), { width: '500', height: '400'}); - -const getRegistrationShowURL = id => STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/registrations/show/${id}`); +const addDeployment = () => STUDIP.Dialog.fromURL(addDeploymentURL(props.registration.id), { width: '500', height: '400'}); @@ -26,7 +25,7 @@ const getRegistrationShowURL = id => STUDIP.URLHelper.getURL(`dispatch.php/admin
diff --git a/resources/vue/apps/lti/registrations/Edit.vue b/resources/vue/apps/lti/registrations/Edit.vue index 71ce82b..af885e2 100644 --- a/resources/vue/apps/lti/registrations/Edit.vue +++ b/resources/vue/apps/lti/registrations/Edit.vue @@ -17,16 +17,8 @@ defineProps({ {{ $gettext('Bitte beachten Sie das geltende europäische Datenschutzrecht (DSGVO)!') }} - -
- - -
-
+ + diff --git a/resources/vue/apps/lti/registrations/Index.vue b/resources/vue/apps/lti/registrations/Index.vue index 085ce86..be7ba4e 100644 --- a/resources/vue/apps/lti/registrations/Index.vue +++ b/resources/vue/apps/lti/registrations/Index.vue @@ -5,6 +5,14 @@ import {computed, ref} from "vue"; import StudipActionMenu from "../../../components/StudipActionMenu.vue"; import StudipDateTime from "../../../components/StudipDateTime.vue"; import StudipIcon from "../../../components/StudipIcon.vue"; +import { + addDeploymentURL, + createRegistrationURL, + deleteRegistrationURL, + editRegistrationURL, + showRangeURL, + showRegistrationURL +} from "../../../components/lti/helpers/urls"; const CSRF = STUDIP.CSRF_TOKEN; @@ -47,14 +55,12 @@ const pageTitle = computed(() => { return $gettext('LTI-Registrierungen'); }); -const addRegistration = () => STUDIP.Dialog.fromURL(STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/registrations/create?role=${props.role}`), { width: '900' }); +const addRegistration = () => STUDIP.Dialog.fromURL(createRegistrationURL(props.role), { width: '900' }); -const addDeployment = registrationId => STUDIP.Dialog.fromURL(STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/deployments/create?registration_id=${registrationId}`), { width: '500', height: '400'}); +const addDeployment = registrationId => STUDIP.Dialog.fromURL(addDeploymentURL(registrationId), { width: '500', height: '400'}); -const getRegistrationShowURL = id => STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/registrations/show/${id}?role=${props.role}`); -const editRegistration = id => STUDIP.Dialog.fromURL(STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/registrations/edit/${id}?role=${props.role}`), { width: '900' }); +const editRegistration = id => STUDIP.Dialog.fromURL(editRegistrationURL(id, props.role), { width: '900' }); const getDeploymentsURL = id => STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/deployments?registration_id=${id}&role=${props.role}`); -const getRangeURL = range_id => STUDIP.URLHelper.getURL(`dispatch.php/course/details/index/${range_id}`); const showConfirmDelete = (id, name) => STUDIP.Dialog.confirm( $gettext('Wollen Sie diese "%{name}" Registrierung löschen?', {name}), () => deleteRegistration(id), @@ -63,7 +69,7 @@ const showConfirmDelete = (id, name) => STUDIP.Dialog.confirm( const deleteRegistration = id => { const deleteForm = document.getElementById('lti-registration-delete-form'); - deleteForm.action = STUDIP.URLHelper.getURL(`dispatch.php/admin/lti/registrations/delete/${id}`); + deleteForm.action = deleteRegistrationURL(id); deleteForm.submit(); } @@ -95,6 +101,7 @@ const deleteRegistration = id => { { { { { { { {{ $gettext('Erstellt am') }} - {{ $gettext('Aktionen') }} + {{ $gettext('Aktionen') }} {{ registration.name }} @@ -190,7 +202,7 @@ const deleteRegistration = id => { - + {{ registration.range_name }} +