<?php /** * 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; under version 2 * of the License (non-upgradable). * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright (c) 2013-2021 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT); */ namespace oat\ltiDeliveryProvider\controller; use common_ext_ExtensionsManager; use common_Logger; use common_session_SessionManager; use core_kernel_classes_Resource; use oat\ltiDeliveryProvider\model\execution\implementation\Lti1p3DeliveryExecutionService; use tao_helpers_I18n; use function GuzzleHttp\Psr7\stream_for; use oat\ltiDeliveryProvider\model\execution\LtiDeliveryExecutionService; use oat\ltiDeliveryProvider\model\LtiAssignment; use oat\ltiDeliveryProvider\model\LTIDeliveryTool; use oat\ltiDeliveryProvider\model\LtiLaunchDataService; use oat\tao\model\actionQueue\ActionFullException; use oat\taoDelivery\model\execution\DeliveryExecution; use oat\taoDelivery\model\execution\StateServiceInterface; use oat\taoLti\controller\ToolModule; use oat\taoLti\models\classes\LtiException; use oat\taoLti\models\classes\LtiMessages\LtiErrorMessage; use oat\taoLti\models\classes\LtiRoles; use oat\taoLti\models\classes\LtiService; use oat\taoLti\models\classes\LtiVariableMissingException; use oat\taoQtiTest\models\QtiTestExtractionFailedException; use tao_helpers_Uri; use oat\ltiDeliveryProvider\model\navigation\LtiNavigationService; class DeliveryTool extends ToolModule { /** * Setting this parameter to 'true' will prevent resuming a testsession in progress * and will start a new testsession whenever the lti tool is launched * * @var string */ const PARAM_FORCE_RESTART = 'custom_force_restart'; /** * Setting this parameter to 'true' will prevent the thank you screen to be shown after * the test and skip directly to the return url * * @var string */ const PARAM_SKIP_THANKYOU = 'custom_skip_thankyou'; /** * Setting this parameter to 'true' will prevent the 'You have already taken this test' * screen to be shown skip directly to the return url * @var string */ const PARAM_SKIP_OVERVIEW = 'custom_skip_overview'; /** * Setting this parameter to a string will show this string as the title of the thankyou * page. (no effect if PARAM_SKIP_THANKYOU is set to 'true') * * @var string */ const PARAM_THANKYOU_MESSAGE = 'custom_message'; /** * (non-PHPdoc) * @see ToolModule::run() * * @throws LtiException * @throws \InterruptedActionException * @throws \ResolverException * @throws \common_exception_Error * @throws \common_exception_IsAjaxAction * @throws \common_exception_NotFound * @throws LtiVariableMissingException */ public function run() { $compiledDelivery = $this->getDelivery(); if (is_null($compiledDelivery) || !$compiledDelivery->exists()) { if ($this->hasAccess(LinkConfiguration::class, 'configureDelivery')) { // user authorised to select the Delivery $this->redirect(tao_helpers_Uri::url('configureDelivery', 'LinkConfiguration', null)); } else { // user NOT authorised to select the Delivery throw new LtiException( __('This tool has not yet been configured, please contact your instructor'), LtiErrorMessage::ERROR_INVALID_PARAMETER ); } } else { $user = common_session_SessionManager::getSession()->getUser(); $isLearner = !is_null($user) && count(array_intersect([LtiRoles::CONTEXT_LEARNER, LtiRoles::CONTEXT_LTI1P3_LEARNER], $user->getRoles())) > 0; if ($isLearner) { if ($this->hasAccess(DeliveryRunner::class, 'runDeliveryExecution')) { try { $activeExecution = $this->getActiveDeliveryExecution($compiledDelivery); if ($activeExecution && $activeExecution->getState()->getUri() != DeliveryExecution::STATE_PAUSED) { $deliveryExecutionStateService = $this->getServiceLocator()->get(StateServiceInterface::SERVICE_ID); $deliveryExecutionStateService->pause($activeExecution); } $this->redirect($this->getLearnerUrl($compiledDelivery, $activeExecution)); } catch (QtiTestExtractionFailedException $e) { common_Logger::i($e->getMessage()); throw new LtiException($e->getMessage()); } catch (ActionFullException $e) { $this->redirect(_url('launchQueue', 'DeliveryTool', null, [ 'position' => $e->getPosition(), 'delivery' => $compiledDelivery->getUri(), ])); } } else { common_Logger::e('Lti learner has no access to delivery runner'); $this->returnError(__('Access to this functionality is restricted'), false); } } elseif ($this->hasAccess(LinkConfiguration::class, 'configureDelivery')) { $this->redirect(_url('showDelivery', 'LinkConfiguration', null, ['uri' => $compiledDelivery->getUri()])); } else { $this->returnError(__('Access to this functionality is restricted to students'), false); } } } /** * @throws LtiException * @throws \common_exception_Error * @throws \common_ext_ExtensionException */ public function launchQueue() { $this->configureI18n(); $delivery = $this->getDelivery(); if (!$delivery->exists()) { throw new LtiException( __('Delivery does not exist. Please contact your instructor.'), LtiErrorMessage::ERROR_INVALID_PARAMETER ); } $runUrl = _url('run', 'DeliveryTool', null, ['delivery' => $delivery->getUri()]); $config = $this->getServiceLocator()->get('ltiDeliveryProvider/LaunchQueue')->getConfig(); $config['runUrl'] = $runUrl; $config['capacityCheckUrl'] = _url('checkCapacity', 'DeliveryTool'); $this->defaultData(); $this->setData('delivery', $delivery); $this->setData('position', intval($this->getRequestParameter('position'))); $this->setData('client_params', $config); $this->setView('learner/launchQueue.tpl'); } public function checkCapacity() { /** @var \oat\taoDelivery\model\Capacity\CapacityInterface $capacityService */ $capacityService = $this->getServiceLocator()->get(\oat\taoDelivery\model\Capacity\CapacityInterface::SERVICE_ID); $capacity = $capacityService->getCapacity(); $payload = [ 'id' => '', 'status' => 0, ]; if ($capacity === -1 || $capacity > 0) { $payload['status'] = 1; } return $this->getPsrResponse()->withBody(stream_for(json_encode($payload))) ->withHeader('Content-Type', 'application/json'); } public function launch1p3(): void { $message = $this->getValidatedLtiMessagePayload(); LtiService::singleton()->startLti1p3Session($message); $this->forward('run', null, null, $_GET); } /** * @param core_kernel_classes_Resource $delivery * @param DeliveryExecution $activeExecution * * @return string * @throws LtiException * @throws \common_exception_Error */ protected function getLearnerUrl(\core_kernel_classes_Resource $delivery, DeliveryExecution $activeExecution = null) { $currentSession = \common_session_SessionManager::getSession(); $user = $currentSession->getUser(); if ($activeExecution === null) { $activeExecution = $this->getActiveDeliveryExecution($delivery); } if ($activeExecution !== null) { return _url('runDeliveryExecution', 'DeliveryRunner', null, ['deliveryExecution' => $activeExecution->getIdentifier()]); } /** @var LtiAssignment $assignmentService */ $assignmentService = $this->getServiceLocator()->get(LtiAssignment::SERVICE_ID); if (!$assignmentService->isDeliveryExecutionAllowed($delivery->getUri(), $user)) { throw new LtiException( __('User is not authorized to run this delivery'), LtiErrorMessage::ERROR_LAUNCH_FORBIDDEN ); } if ($user->getLaunchData()->hasVariable(self::PARAM_SKIP_OVERVIEW)) { $executionService = $this->getServiceLocator()->get(LtiDeliveryExecutionService::SERVICE_ID); $executions = $executionService->getLinkedDeliveryExecutions($delivery, $currentSession->getLtiLinkResource(), $user->getIdentifier()); $lastDE = end($executions); /** @var LtiNavigationService $ltiNavigationService */ $ltiNavigationService = $this->getServiceLocator()->get(LtiNavigationService::SERVICE_ID); $url = $ltiNavigationService->getReturnUrl($user->getLaunchData(), $lastDE); } else { $url = _url( 'ltiOverview', 'DeliveryRunner', null, ['delivery' => $delivery->getUri()] ); } return $url; } /** * @param core_kernel_classes_Resource $delivery * * @return mixed|null|DeliveryExecution */ protected function getActiveDeliveryExecution(\core_kernel_classes_Resource $delivery) { $deliveryExecutionService = $this->getServiceLocator()->get(LtiDeliveryExecutionService::SERVICE_ID); return $deliveryExecutionService->getActiveDeliveryExecution($delivery); } /** * (non-PHPdoc) * @see ToolModule::getTool() */ protected function getTool() { return $this->getServiceLocator()->get(LTIDeliveryTool::class); } /** * Returns the delivery associated with the current link * either from url or from the remote_link if configured * returns null if none found * * @return core_kernel_classes_Resource * @throws LtiException * @throws \common_exception_Error */ protected function getDelivery() { //passed as a parameter if ($this->hasRequestParameter('delivery')) { $returnValue = new core_kernel_classes_Resource($this->getRequestParameter('delivery')); } else { $launchData = LtiService::singleton()->getLtiSession()->getLaunchData(); /** @var LtiLaunchDataService $launchDataService */ $launchDataService = $this->getServiceLocator()->get(LtiLaunchDataService::SERVICE_ID); $returnValue = $launchDataService->findDeliveryFromLaunchData($launchData); } return $returnValue; } private function configureI18n(): void { $extension = $this->getServiceLocator() ->get(common_ext_ExtensionsManager::SERVICE_ID) ->getExtensionById('ltiDeliveryProvider'); tao_helpers_I18n::init($extension, DEFAULT_ANONYMOUS_INTERFACE_LANG); } }