1<?php
2/*
3    +-----------------------------------------------------------------------------+
4    | ILIAS open source                                                           |
5    +-----------------------------------------------------------------------------+
6    | Copyright (c) 1998-2001 ILIAS open source, University of Cologne            |
7    |                                                                             |
8    | This program is free software; you can redistribute it and/or               |
9    | modify it under the terms of the GNU General Public License                 |
10    | as published by the Free Software Foundation; either version 2              |
11    | of the License, or (at your option) any later version.                      |
12    |                                                                             |
13    | This program is distributed in the hope that it will be useful,             |
14    | but WITHOUT ANY WARRANTY; without even the implied warranty of              |
15    | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               |
16    | GNU General Public License for more details.                                |
17    |                                                                             |
18    | You should have received a copy of the GNU General Public License           |
19    | along with this program; if not, write to the Free Software                 |
20    | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. |
21    +-----------------------------------------------------------------------------+
22*/
23
24use ILIAS\LTI\Screen\LtiViewLayoutProvider;
25
26/**
27 * @classDescription class for ILIAS ViewLTI
28 *
29 * @author Stefan Schneider <schneider@hrz.uni-marburg.de
30 * @version $id$
31 * @ingroup ServicesLTI
32 * @ilCtrl_IsCalledBy ilLTIViewGUI: ilLTIRouterGUI
33 *
34 */
35class ilLTIViewGUI
36{
37    /**
38     * contstants
39     */
40    const CHECK_HTTP_REFERER = true;
41
42    /**
43     * private variables
44     */
45    private $dic = null;
46    private $user = null;
47    private $log = null;
48    private $link_dir = "";
49
50    /**
51     * public variables
52     */
53    public $lng = null;
54
55    public function __construct()
56    {
57        global $DIC;
58        $this->dic = $DIC;
59        $this->log = $this->dic->logger()->lti();
60        $this->lng = $this->dic->language();
61        $this->lng->loadLanguageModule('lti');
62    }
63
64    /**
65     * Init LTI mode for lti authenticated users
66     */
67    public function init()
68    {
69        $this->link_dir = (defined("ILIAS_MODULE")) ? "../" : "";
70		if ($this->isLTIUser())
71		{
72			$context = $this->dic->globalScreen()->tool()->context();
73			$context->claim()->lti();
74			$this->initGUI();
75        }
76    }
77
78    /**
79     * for compatiblity with ilLTIRouterGUI
80     */
81    public static function getInstance()
82    {
83        global $DIC;
84        return $DIC["lti"];
85    }
86
87    /**
88     * get LTI Mode from Users->getAuthMode
89     * @return boolean
90     */
91    private function isLTIUser()
92    {
93        if (!$this->dic->user() instanceof ilObjUser) {
94            return false;
95        }
96        return (strpos($this->dic->user()->getAuthMode(), 'lti_') === 0);
97    }
98
99    public function executeCommand()
100    {
101        global $ilCtrl;
102        $cmd = $ilCtrl->getCmd();
103        switch ($cmd) {
104            case 'exit':
105                $this->exitLti();
106                break;
107        }
108    }
109
110    public function isActive() : bool
111    {
112		return $this->isLTIUser();
113    }
114
115    public function initGUI()
116    {
117        $this->log->debug("initGUI");
118        $baseclass = strtolower($_GET['baseClass']);
119        $cmdclass = strtolower($_GET['cmdClass']);
120		switch ($baseclass)
121		{
122			case 'illtiroutergui' :
123				return;
124				break;
125		}
126	}
127
128	public function getContextId() {
129        global $ilLocator;
130
131        // forced lti_context_id for example request command in exitLTI
132        if (isset($_GET['lti_context_id']) && $_GET['lti_context_id'] !== '') {
133            $this->log->debug("find context_id by GET param: " . $_GET['lti_context_id']);
134			return $_GET['lti_context_id'];
135        }
136
137        $ref_id = $this->findEffectiveRefId();
138        $this->log->debug("Effective ref_id: ". $ref_id);
139        // context_id = ref_id in request
140        if (isset($_SESSION['lti_' . $ref_id . '_post_data'])) {
141            $this->log->debug("lti context session exists for " . $ref_id);
142            return $ref_id;
143        }
144
145        // sub item request
146        $this->log->debug("ref_id not exists as context_id, walking tree backwards to find a valid context_id");
147        $locator_items = $ilLocator->getItems();
148		if (is_array($locator_items) && count($locator_items) > 0) {
149            for ($i = count($locator_items)-1;$i>=0;$i--) {
150                if (isset($_SESSION['lti_' . $locator_items[$i]['ref_id'] . '_post_data'])) {
151                    $this->log->debug("found valid ref_id in locator: " . $locator_items[$i]['ref_id']);
152                    return $locator_items[$i]['ref_id'];
153                }
154            }
155        }
156        $this->log->warning("no valid context_id found for ref_id request: " . $ref_id);
157
158        if (ilLTIViewGUI::CHECK_HTTP_REFERER) {
159            $ref_id = '';
160            $obj_type = '';
161            $context_id = '';
162            $referer = '';
163
164            // first try to get real http referer
165            if (isset($_SERVER['HTTP_REFERER'])) {
166                $referer = $this->findEffectiveRefId($_SERVER['HTTP_REFERER']);
167            }
168            else { // only fallback and not reliable on multiple browser LTi contexts
169                if (isset($_SESSION['referer_ref_id'])) {
170                    $referer = $_SESSION['referer_ref_id'];
171                }
172            }
173
174            if ($referer != '') {
175                if (isset($_SESSION['lti_' . $referer . '_post_data'])) {
176                    $ref_id =$referer;
177                    $context_id = $referer;
178                    $obj_type = ilObject::_lookupType($ref_id,true);
179                    $this->log->debug("referer obj_type: " . $obj_type);
180                }
181                else {
182                    $this->log->debug("search tree of referer...");
183                    if ($this->dic->repositoryTree()->isInTree($referer)) {
184                        $path = $this->dic->repositoryTree()->getPathId($referer);
185                        for ($i = count($path)-1;$i>=0;$i--) {
186                            if (isset($_SESSION['lti_' . $path[$i] . '_post_data'])) {
187                                // redirect to referer, because it is valid
188                                $ref_id = $referer;
189                                $context_id = $path[$i];
190                                $obj_type = ilObject::_lookupType($ref_id,true);
191                                break;
192                            }
193                        }
194                    }
195                }
196            }
197            if ($ref_id != '' && $obj_type != '') {
198                ilUtil::sendFailure($this->lng->txt('permission_denied'),true);
199                $redirect = $this->link_dir."goto.php?target=".$obj_type."_".$ref_id."&lti_context_id=".$context_id;
200                $this->log->debug("redirect: " . $redirect);
201                ilUtil::redirect($redirect);
202            }
203        }
204        $lti_context_ids = $_SESSION['lti_context_ids'];
205        if (is_array($lti_context_ids) && count($lti_context_ids) > 0) {
206            if (count($lti_context_ids) == 1) {
207                $this->log->debug("using context_id from only LTI session");
208                return $lti_context_ids[0];
209            }
210            else {
211                $this->log->warning("Multiple LTI sessions exists. The context_id can not be clearly detected");
212            }
213        }
214		return '';
215    }
216
217	public function getPostData() {
218        $context_id = $this->getContextId();
219        if ($context_id == '') {
220            $this->log->warning("could not find any valid context_id!");
221            return null;
222        }
223		$post_data = $_SESSION['lti_' . $this->getContextId() . '_post_data'];
224		if (!is_array($post_data)) {
225			$this->log->warning("no session post_data: " . "lti_" . $this->getContextId() . "_post_data");
226			return null;
227        }
228		return $post_data;
229    }
230
231	public function getExternalCss() {
232		$post_data = $this->getPostData();
233		if ($post_data !== null) {
234			return (isset($post_data['launch_presentation_css_url'])) ? $post_data['launch_presentation_css_url'] : '';
235        }
236		return '';
237    }
238
239    public function getTitle() : string
240    {
241		$post_data = $this->getPostData();
242		if ($post_data !== null) {
243			return (isset($post_data['resource_link_title'])) ? "LTI - " . $post_data['resource_link_title'] : "LTI";
244		}
245		return "LTI";
246    }
247
248    public function getTitleForExitPage() : string
249    {
250        return $this->lng->txt('lti_exited');
251    }
252
253    public function getShortTitle() : string
254    {
255        return $this->lng->txt('lti_mode');
256    }
257
258    /**
259     * exit LTI session and if defined redirecting to returnUrl
260     * ToDo: Standard Template with delos ...
261     */
262    public function exitLti()
263    {
264        $this->dic->logger()->lti()->info("exitLTI");
265        $force_ilias_logout = false;
266        $context_id = $this->getContextId();
267        if ($context_id == '') {
268            $this->log->warning("could not find any valid context_id!");
269            $force_ilias_logout = true;
270        }
271		$post_data = $this->getPostData();
272		$return_url = ($post_data !== null) ? $post_data['launch_presentation_return_url'] : '';
273        $this->removeContextFromSession($context_id);
274
275		if (isset($_SESSION['lti_' . $context_id . '_post_data'])) {
276			unset($_SESSION['lti_' . $context_id . '_post_data']);
277			$this->dic->logger()->lti()->debug('unset SESSION["' . 'lti_' . $context_id . '_post_data"]');
278		}
279		if (!isset($return_url) || $return_url === '') {
280            $cc = $this->dic->globalScreen()->tool()->context()->current();
281            $cc->addAdditionalData(LtiViewLayoutProvider::GS_EXIT_LTI, true);
282            $ui_factory = $this->dic->ui()->factory();
283            $renderer = $this->dic->ui()->renderer();
284            $content = [
285                $ui_factory->messageBox()->info($this->lng->txt('lti_exited_info'))
286            ];
287            $tpl = $this->dic["tpl"];
288            $tpl->setContent($renderer->render($content));
289            $this->logout($force_ilias_logout);
290            $tpl->printToStdout();
291        } else {
292			$this->logout($force_ilias_logout);
293			header('Location: ' . $return_url);
294        }
295    }
296
297    /**
298	 * logout ILIAS and destroys Session and ilClientId cookie if no consumer is still open in the LTI User Session
299     */
300    public function logout($force_ilias_logout=false)
301    {
302        if ($force_ilias_logout) {
303            $this->log->warning("forcing logout ilias session, maybe a broken LTI context");
304        }
305        else {
306            if (is_array($_SESSION['lti_context_ids']) && count($_SESSION['lti_context_ids']) > 0) {
307			    $this->log->debug("there is another valid consumer session: ilias session logout refused.");
308                return;
309            }
310        }
311        $this->dic->logger()->lti()->info("logout");
312        $GLOBALS['DIC']->user()->setAuthMode(AUTH_LOCAL);
313        //ilSession::setClosingContext(ilSession::SESSION_CLOSE_USER); // needed?
314        $auth = $GLOBALS['DIC']['ilAuthSession'];
315        //$auth->logout(); // needed?
316        $auth->setExpired($auth::SESSION_AUTH_EXPIRED,ilAuthStatus::STATUS_UNDEFINED);
317        session_destroy();
318        $client_id = $_COOKIE["ilClientId"];
319        ilUtil::setCookie("ilClientId", "");
320		ilUtil::setCookie("PHPSESSID","");
321    }
322
323    public function getCmdLink(String $cmd) : String
324    {
325        global $ilCtrl;
326		$lti_context_id = $this->getContextId();
327		$lti_context_id_param = ($lti_context_id  != '') ? "&lti_context_id=".$lti_context_id : '';
328        $targetScript = ($ilCtrl->getTargetScript() !== 'ilias.php') ? "ilias.php" : "";
329		return $this->link_dir.$targetScript.$ilCtrl->getLinkTargetByClass(array('illtiroutergui',strtolower(get_class($this))),$cmd)."&baseClass=illtiroutergui".$lti_context_id_param;
330    }
331
332    private function getSessionValue(String $sess_key) : String
333    {
334        if (isset($_SESSION[$sess_key]) && $_SESSION[$sess_key] != '') {
335            return $_SESSION[$sess_key];
336        } else {
337            return '';
338        }
339    }
340
341	private function getCookieValue(String $cookie_key) : String
342	{
343		if (isset($_COOKIE[$cookie_key]) && $_COOKIE[$cookie_key] != '') {
344			return $_COOKIE[$cookie_key];
345		}
346		else {
347			return '';
348		}
349	}
350
351	private function removeContextFromSession($context_id) {
352		$lti_context_ids = $_SESSION['lti_context_ids'];
353		if (is_array($lti_context_ids) && in_array($context_id,$lti_context_ids)) {
354			array_splice($lti_context_ids,array_search($context_id,$lti_context_ids),1);
355			$_SESSION['lti_context_ids'] = $lti_context_ids;
356		}
357    }
358
359    /**
360     * Find effective ref_id for request
361     */
362    private function findEffectiveRefId($url=null)
363    {
364        if ($url === null) {
365            $query = $_GET;
366        }
367        else {
368            parse_str(parse_url($url, PHP_URL_QUERY),$query);
369        }
370        if ((int) $query['ref_id']) {
371            return (int) $query['ref_id'];
372        }
373        $target_arr = explode('_', (string) $query['target']);
374        if (isset($target_arr[1]) and (int) $target_arr[1]) {
375            return (int) $target_arr[1];
376        }
377        return '';
378    }
379}
380