1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\FrontendLogin\Redirect;
19
20use TYPO3\CMS\Core\Authentication\LoginType;
21use TYPO3\CMS\Core\Context\Context;
22use TYPO3\CMS\FrontendLogin\Configuration\RedirectConfiguration;
23
24/**
25 * Resolve felogin related redirects based on the current login type and the selected configuration (redirect mode)
26 *
27 * @internal this is a concrete TYPO3 implementation and solely used for EXT:felogin and not part of TYPO3's Core API.
28 */
29class RedirectHandler
30{
31    /**
32     * @var bool
33     */
34    protected $userIsLoggedIn = false;
35
36    /**
37     * @var ServerRequestHandler
38     */
39    protected $requestHandler;
40
41    /**
42     * @var RedirectModeHandler
43     */
44    protected $redirectModeHandler;
45
46    public function __construct(
47        ServerRequestHandler $requestHandler,
48        RedirectModeHandler $redirectModeHandler,
49        Context $context
50    ) {
51        $this->requestHandler = $requestHandler;
52        $this->redirectModeHandler = $redirectModeHandler;
53        $this->userIsLoggedIn = (bool)$context->getPropertyFromAspect('frontend.user', 'isLoggedIn');
54    }
55
56    /**
57     * Process redirect modes. The function searches for a redirect url using all configured modes.
58     *
59     * @param string $loginType
60     * @param RedirectConfiguration $configuration
61     * @param string $redirectModeReferrer
62     * @return string Redirect URL
63     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
64     */
65    public function processRedirect(string $loginType, RedirectConfiguration $configuration, string $redirectModeReferrer): string
66    {
67        if ($this->isUserLoginFailedAndLoginErrorActive($configuration->getModes(), $loginType)) {
68            return $this->redirectModeHandler->redirectModeLoginError($configuration->getPageOnLoginError());
69        }
70
71        $redirectUrlList = [];
72        foreach ($configuration->getModes() as $redirectMode) {
73            $redirectUrl = '';
74
75            if ($loginType === LoginType::LOGIN) {
76                $redirectUrl = $this->handleSuccessfulLogin($redirectMode, $configuration->getPageOnLogin(), $configuration->getDomains(), $redirectModeReferrer);
77            } elseif ($loginType === LoginType::LOGOUT) {
78                $redirectUrl = $this->handleSuccessfulLogout($redirectMode, $configuration->getPageOnLogout());
79            }
80
81            if ($redirectUrl !== '') {
82                $redirectUrlList[] = $redirectUrl;
83            }
84        }
85
86        return $this->fetchReturnUrlFromList($redirectUrlList, $configuration->getFirstMode());
87    }
88
89    /**
90     * Get alternative logout form redirect url if logout and page not accessible
91     *
92     * @param array $redirectModes
93     * @param int $redirectPageLogout
94     * @return string
95     */
96    protected function getLogoutRedirectUrl(array $redirectModes, int $redirectPageLogout = 0): string
97    {
98        if ($this->userIsLoggedIn && $this->isRedirectModeActive($redirectModes, RedirectMode::LOGOUT)) {
99            return $this->redirectModeHandler->redirectModeLogout($redirectPageLogout);
100        }
101        return $this->getGetpostRedirectUrl($redirectModes);
102    }
103
104    /**
105     * Get alternative login form redirect url
106     *
107     * @param array $redirectModes
108     * @param int $redirectPageLogin
109     * @return string
110     */
111    protected function getLoginRedirectUrl(array $redirectModes, int $redirectPageLogin): string
112    {
113        if ($this->isRedirectModeActive($redirectModes, RedirectMode::LOGIN)) {
114            return $this->redirectModeHandler->redirectModeLogin($redirectPageLogin);
115        }
116        return $this->getGetpostRedirectUrl($redirectModes);
117    }
118
119    /**
120     * Is used for alternative redirect urls on redirect mode "getpost"
121     *
122     * @param array $redirectModes
123     * @return string
124     */
125    protected function getGetpostRedirectUrl(array $redirectModes): string
126    {
127        return $this->isRedirectModeActive($redirectModes, RedirectMode::GETPOST)
128            ? $this->requestHandler->getRedirectUrlRequestParam()
129            : '';
130    }
131
132    /**
133     * Handle redirect mode logout
134     *
135     * @param string $redirectMode
136     * @param int $redirectPageLogout
137     * @return string
138     */
139    protected function handleSuccessfulLogout(string $redirectMode, int $redirectPageLogout): string
140    {
141        if ($redirectMode === RedirectMode::LOGOUT) {
142            return $this->redirectModeHandler->redirectModeLogout($redirectPageLogout);
143        }
144        return '';
145    }
146
147    /**
148     * Base on setting redirectFirstMethod get first or last entry from redirect url list.
149     *
150     * @param array $redirectUrlList
151     * @param string $redirectFirstMethod
152     * @return string
153     */
154    protected function fetchReturnUrlFromList(array $redirectUrlList, $redirectFirstMethod): string
155    {
156        if (count($redirectUrlList) === 0) {
157            return '';
158        }
159
160        // Remove empty values, but keep "0" as value (that's why "strlen" is used as second parameter)
161        $redirectUrlList = array_filter($redirectUrlList, static function (string $value): bool {
162            return strlen($value) > 0;
163        });
164
165        return $redirectFirstMethod
166            ? array_shift($redirectUrlList)
167            : array_pop($redirectUrlList);
168    }
169
170    /**
171     * Generate redirect_url for case that the user was successfully logged in
172     *
173     * @param string $redirectMode
174     * @param int $redirectPageLogin
175     * @param string $domains
176     * @param string $redirectModeReferrer
177     * @return string
178     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
179     */
180    protected function handleSuccessfulLogin(string $redirectMode, int $redirectPageLogin = 0, string $domains = '', string $redirectModeReferrer = ''): string
181    {
182        if (!$this->userIsLoggedIn) {
183            return '';
184        }
185
186        // Logintype is needed because the login-page wouldn't be accessible anymore after a login (would always redirect)
187        switch ($redirectMode) {
188            case RedirectMode::GROUP_LOGIN:
189                $redirectUrl = $this->redirectModeHandler->redirectModeGroupLogin();
190                break;
191            case RedirectMode::USER_LOGIN:
192                $redirectUrl = $this->redirectModeHandler->redirectModeUserLogin();
193                break;
194            case RedirectMode::LOGIN:
195                $redirectUrl = $this->redirectModeHandler->redirectModeLogin($redirectPageLogin);
196                break;
197            case RedirectMode::GETPOST:
198                $redirectUrl = $this->requestHandler->getRedirectUrlRequestParam();
199                break;
200            case RedirectMode::REFERER:
201                $redirectUrl = $this->redirectModeHandler->redirectModeReferrer($redirectModeReferrer);
202                break;
203            case RedirectMode::REFERER_DOMAINS:
204                $redirectUrl = $this->redirectModeHandler->redirectModeRefererDomains($domains, $redirectModeReferrer);
205                break;
206            default:
207                $redirectUrl = '';
208        }
209
210        return $redirectUrl;
211    }
212
213    protected function isUserLoginFailedAndLoginErrorActive(array $redirectModes, string $loginType): bool
214    {
215        return $loginType === LoginType::LOGIN
216            && !$this->userIsLoggedIn
217            && $this->isRedirectModeActive($redirectModes, RedirectMode::LOGIN_ERROR);
218    }
219
220    /**
221     * Checks if the give mode is active or not
222     *
223     * @param string $mode
224     * @param array $redirectModes
225     * @return bool
226     */
227    protected function isRedirectModeActive(array $redirectModes, string $mode): bool
228    {
229        return in_array($mode, $redirectModes, true);
230    }
231
232    /**
233     * Returns the redirect Url that should be used in login form
234     *
235     * @param RedirectConfiguration $configuration
236     * @param bool $redirectDisabled
237     * @return string
238     */
239    public function getLoginFormRedirectUrl(RedirectConfiguration $configuration, bool $redirectDisabled): string
240    {
241        if (!$redirectDisabled) {
242            return $this->getLoginRedirectUrl($configuration->getModes(), $configuration->getPageOnLogin());
243        }
244        return $this->requestHandler->getRedirectUrlRequestParam();
245    }
246
247    /**
248     * Returns the redirect Url that should be used in logout form
249     *
250     * @param RedirectConfiguration $configuration
251     * @param int $redirectPageLogout
252     * @param bool $redirectDisabled
253     * @return string
254     */
255    public function getLogoutFormRedirectUrl(RedirectConfiguration $configuration, int $redirectPageLogout, bool $redirectDisabled): string
256    {
257        if (!$redirectDisabled) {
258            return $this->getLogoutRedirectUrl($configuration->getModes(), $redirectPageLogout);
259        }
260        return $this->requestHandler->getRedirectUrlRequestParam();
261    }
262}
263