Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
LtiReturnResponse
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 12
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHttpCode
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 send
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 requiresRedirect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLtiErrorMessage
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 showLtiErrorPage
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 renderLtiErrorPage
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 errorRedirectResponse
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getRedirectUrl
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getReturnBaseUrl
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 ltiRedirect
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 responseWithCode
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2017 (original work) Open Assessment Technologies SA
19 *
20 */
21
22namespace oat\taoLti\models\classes;
23
24use Renderer;
25use HTTPToolkit;
26use common_http_Request;
27use oat\tao\helpers\Template;
28use oat\tao\model\mvc\error\ResponseAbstract;
29use oat\taoLti\models\classes\LtiMessages\LtiErrorMessage;
30
31/**
32 * Send LTI error response.
33 *
34 * @package oat\taoLti\models\classes
35 * @author Aleh Hutnikau, <hutnikau@1pt.com>
36 */
37class LtiReturnResponse extends ResponseAbstract
38{
39    protected $httpCode = 302;
40
41    /**
42     * @var LtiException
43     */
44    protected $exception;
45
46    protected $requestParams;
47
48    /**
49     * @var LtiLaunchData
50     */
51    protected $launchData;
52
53    /**
54     * @var Renderer
55     */
56    private $renderer;
57
58    public function __construct(Renderer $renderer)
59    {
60        $this->renderer = $renderer;
61    }
62
63    /**
64     * @param int $code
65     * @return ResponseAbstract
66     */
67    public function setHttpCode($code)
68    {
69        $this->httpCode = $code;
70        return $this;
71    }
72
73    /**
74     * Send LTI error response.
75     */
76    public function send()
77    {
78        try {
79            $this->requestParams = common_http_Request::currentRequest()->getParams();
80            $this->launchData = LtiLaunchData::fromRequest(common_http_Request::currentRequest());
81            $baseUrl = null;
82
83            if ($this->requiresRedirect() && !empty($this->getReturnBaseUrl())) {
84                $this->errorRedirectResponse();
85            } else {
86                $this->responseWithCode(400, $this->showLtiErrorPage());
87            }
88        } catch (\Exception $e) {
89            $this->renderer->setTemplate(Template::getTemplate('error/error500.tpl', 'tao'));
90            echo $this->renderer->render();
91        }
92    }
93
94    /**
95     * Check if redirect error response is required.
96     *
97     * @return bool
98     */
99    protected function requiresRedirect()
100    {
101        return $this->exception instanceof LtiClientException;
102    }
103
104    /**
105     * Generate LtiErrorMessage based on exception
106     *
107     * @return LtiErrorMessage
108     */
109    protected function getLtiErrorMessage()
110    {
111        $message = __('Error: ') . $this->exception->getMessage();
112        $log = __('Error: [key %s] "%s"', $this->exception->getKey(), $this->exception->getMessage());
113        return new LtiErrorMessage($message, $log);
114    }
115
116    /**
117     * Show error page
118     *
119     * @return string
120     *
121     * @throws LtiVariableMissingException
122     * @throws \common_Exception
123     */
124    protected function showLtiErrorPage(): string
125    {
126        if (isset($this->requestParams[LtiLaunchData::TOOL_CONSUMER_INSTANCE_NAME])) {
127            $this->renderer->setData(
128                'consumerLabel',
129                $this->requestParams[LtiLaunchData::TOOL_CONSUMER_INSTANCE_NAME]
130            );
131        } elseif (isset($this->requestParams[LtiLaunchData::TOOL_CONSUMER_INSTANCE_DESCRIPTION])) {
132            $this->renderer->setData(
133                'consumerLabel',
134                $this->requestParams[LtiLaunchData::TOOL_CONSUMER_INSTANCE_DESCRIPTION]
135            );
136        }
137
138        if (isset($this->requestParams[LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL])) {
139            $returnUrl = $this->requestParams[LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL];
140            $serverName = $_SERVER['SERVER_NAME'];
141            $pieces = parse_url($returnUrl);
142            $domain = isset($pieces['host']) ? $pieces['host'] : '';
143            if ($serverName == $domain) {
144                $this->renderer->setData('returnUrl', $returnUrl);
145            }
146        }
147
148        return $this->renderLtiErrorPage($this->exception, false);
149    }
150
151    /**
152     * Render an error page.
153     *
154     * Ignore the parameter returnLink as LTI session always
155     * require a way for the consumer to return to his platform
156     *
157     * @param LtiException $error
158     * @param bool $returnLink
159     *
160     * @return string
161     *
162     * @throws LtiVariableMissingException
163     * @throws \common_Exception
164     */
165    protected function renderLtiErrorPage(LtiException $error, $returnLink = true): string
166    {
167        // In regard of the IMS LTI standard, we have to show a back button that refer to the
168        // launch_presentation_return_url url param. So we have to retrieve this parameter before trying to start
169        // te session
170        $consumerLabel = $this->launchData->getToolConsumerName();
171        if (!is_null($consumerLabel)) {
172            $this->renderer->setData('consumerLabel', $consumerLabel);
173        }
174
175        $this->renderer->setData('message', $error->getMessage());
176        $this->renderer->setTemplate(Template::getTemplate('error.tpl', 'taoLti'));
177
178        return $this->renderer->render();
179    }
180
181    /**
182     * Send LTI error redirect response.
183     *
184     * @throws LtiException
185     * @throws \common_exception_Error
186     */
187    private function errorRedirectResponse()
188    {
189        $queryParams = $this->getLtiErrorMessage()->getUrlParams();
190        $url = $this->getRedirectUrl($queryParams);
191
192        $this->ltiRedirect($url);
193    }
194
195    /**
196     * Build LTI return url with query parameters.
197     *
198     * @param array $queryParams
199     * @return string
200     * @throws LtiException
201     * @throws \common_exception_Error
202     */
203    private function getRedirectUrl(array $queryParams)
204    {
205        $baseUrl = $this->getReturnBaseUrl();
206
207        if (!empty($baseUrl)) {
208            return $baseUrl . (parse_url($baseUrl, PHP_URL_QUERY) ? '&' : '?') . http_build_query($queryParams);
209        } else {
210            throw new LtiException('Invalid LTI return url.');
211        }
212    }
213
214    /**
215     * Get lti return url from LTI session or from request data.
216     *
217     * @return string
218     * @throws LtiException
219     * @throws \common_exception_Error
220     */
221    private function getReturnBaseUrl()
222    {
223        $baseUrl = '';
224
225        /** @var TaoLtiSession $session */
226        $session = \common_session_SessionManager::getSession();
227        if ($session instanceof TaoLtiSession) {
228            $launchData = $session->getLaunchData();
229            if ($launchData->hasReturnUrl()) {
230                $baseUrl = $launchData->getReturnUrl();
231            }
232        } else {
233            if ($this->launchData->hasVariable(LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL)) {
234                $baseUrl = $this->launchData->getVariable(LtiLaunchData::LAUNCH_PRESENTATION_RETURN_URL);
235            }
236        }
237
238        return $baseUrl;
239    }
240
241    /**
242     * @param $url
243     * @param int $statusCode
244     */
245    private function ltiRedirect($url, $statusCode = 302)
246    {
247        header(HTTPToolkit::statusCodeHeader($statusCode));
248        header(HTTPToolkit::locationHeader($url));
249    }
250
251    private function responseWithCode(int $statusCode, string $data, string $contentType = 'text/html'): void
252    {
253        $this->setHttpCode($statusCode);
254        $this->contentType = $contentType;
255        $this->sendHeaders();
256        echo $data;
257    }
258}