Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Resolver
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 9
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 getRequest
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExtensionId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getControllerClass
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getMethodName
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getControllerShortName
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 resolve
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getRoutes
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 getRoute
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
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) 2014 (original work) Open Assessment Technologies SA;
19 */
20
21namespace oat\tao\model\routing;
22
23use common_http_Request;
24use GuzzleHttp\Psr7\ServerRequest;
25use oat\oatbox\extension\exception\ManifestNotFoundException;
26use Psr\Http\Message\ServerRequestInterface;
27use Zend\ServiceManager\ServiceLocatorAwareInterface;
28use Zend\ServiceManager\ServiceLocatorAwareTrait;
29
30/**
31 * Resolves a http request to a controller and method
32 * using the provided routers
33 *
34 * @author Joel Bout, <joel@taotesting.com>
35 */
36class Resolver implements ServiceLocatorAwareInterface
37{
38    use ServiceLocatorAwareTrait;
39
40    public const DEFAULT_EXTENSION = 'tao';
41
42    /**
43     * Request to be resolved
44     *
45     * @var ServerRequestInterface
46     */
47    private $request;
48
49    private $extensionId;
50
51    private $controller;
52
53    private $action;
54
55    /** @var array array of available routes indexed by extension identifier */
56    private static $extRoutes = [];
57
58    /**
59     * Resolves a request to a method
60     *
61     * @param common_http_Request|ServerRequestInterface $request
62     *
63     * @throws \common_exception_InvalidArgumentType
64     */
65    public function __construct($request)
66    {
67        if (is_object($request)) {
68            if ($request instanceof \common_http_Request) {
69                /* @var common_http_Request $request */
70                $this->request = new ServerRequest(
71                    $request->getMethod(),
72                    $request->getUrl(),
73                    $request->getHeaders(),
74                    $request->getBody(),
75                    '1.1.',
76                    $request->getParams()
77                );
78                return;
79            } elseif (is_a($request, ServerRequestInterface::class)) {
80                $this->request = $request;
81                return;
82            }
83        }
84
85        throw new \common_exception_InvalidArgumentType(
86            __CLASS__,
87            __FUNCTION__,
88            1,
89            ServerRequestInterface::class,
90            $request
91        );
92    }
93
94    /**
95     * Return the PSR7 request
96     *
97     * @return ServerRequestInterface
98     */
99    public function getRequest()
100    {
101        return $this->request;
102    }
103
104    /**
105     * @throws \ResolverException
106     * @throws \common_exception_InconsistentData
107     * @throws ManifestNotFoundException
108     *
109     * @return string
110     */
111    public function getExtensionId()
112    {
113        if ($this->extensionId === null) {
114            $this->resolve();
115        }
116        return $this->extensionId;
117    }
118
119    /**
120     * @throws \ResolverException
121     * @throws \common_exception_InconsistentData
122     * @throws ManifestNotFoundException
123     *
124     * @return null
125     */
126    public function getControllerClass()
127    {
128        if ($this->controller === null) {
129            $this->resolve();
130        }
131        return $this->controller;
132    }
133
134    /**
135     * @throws \ResolverException
136     * @throws \common_exception_InconsistentData
137     * @throws ManifestNotFoundException
138     *
139     * @return null
140     */
141    public function getMethodName()
142    {
143        if ($this->action === null) {
144            $this->resolve();
145        }
146        return $this->action;
147    }
148
149    /**
150     * Get the controller short name as used into the URL
151     *
152     * @throws \ResolverException
153     *
154     * @return string the name
155     */
156    public function getControllerShortName()
157    {
158        $relativeUrl = $this->request->getUri()->getPath();
159
160        $rootUrl = parse_url(ROOT_URL);
161        if (isset($rootUrl['path'])) {
162            $rootUrlPath = $rootUrl['path'];
163            $relativeUrl = str_replace(rtrim($rootUrlPath, '/'), '', $relativeUrl);
164        }
165
166        $parts = explode('/', trim($relativeUrl, '/'));
167        if (count($parts) === 3) {
168            return $parts[1];
169        }
170
171        return null;
172    }
173
174    /**
175     * Tries to resolve the current request using the routes first
176     * and then falls back to the legacy controllers
177     *
178     * @throws \ResolverException
179     * @throws \common_exception_InconsistentData
180     * @throws ManifestNotFoundException
181     *
182     * @return bool
183     */
184    protected function resolve()
185    {
186        $extensionsManager = $this->getServiceLocator()->get(\common_ext_ExtensionsManager::SERVICE_ID);
187        $installed = $extensionsManager->getInstalledExtensionsIds();
188        foreach ($installed as $extId) {
189            $extension = $extensionsManager->getExtensionById($extId);
190            foreach ($this->getRoutes($extension) as $entry) {
191                /** @var Route $route */
192                $route = $entry['route'];
193                $called = $route->resolve($this->getRequest());
194                if ($called !== null) {
195                    list($controller, $action) = explode('@', $called);
196                    $this->controller = $controller;
197                    $this->action = $action;
198                    $this->extensionId = $entry['extId'];
199
200                    return true;
201                }
202            }
203        }
204
205        throw new \ResolverException('Unable to resolve ' . $this->request->getUri()->getPath());
206    }
207
208    /**
209     * @param \common_ext_Extension $extension
210     *
211     * @throws \common_exception_InconsistentData
212     * @throws ManifestNotFoundException
213     *
214     * @return mixed
215     */
216    private function getRoutes(\common_ext_Extension $extension)
217    {
218        $extId = $extension->getId();
219        if (!isset(self::$extRoutes[$extId])) {
220            $routes = [];
221            foreach ($extension->getManifest()->getRoutes() as $routeId => $routeData) {
222                $routes[] = [
223                    'extId' => $extId,
224                    'route' => $this->getRoute($extension, $routeId, $routeData),
225                ];
226            }
227            if (empty($routes)) {
228                $routes[] = [
229                    'extId' => $extId,
230                    'route' => new LegacyRoute($extension, $extension->getName(), []),
231                ];
232            }
233            self::$extRoutes[$extId] = $routes;
234        }
235        return self::$extRoutes[$extId];
236    }
237
238    /**
239     * @param \common_ext_Extension $extension
240     * @param $routeId
241     * @param $routeData
242     *
243     * @throws \common_exception_InconsistentData
244     *
245     * @return \oat\tao\model\routing\Route
246     */
247    private function getRoute(\common_ext_Extension $extension, $routeId, $routeData)
248    {
249        if (is_string($routeData)) {
250            $routeData = [
251                'class' => NamespaceRoute::class,
252                NamespaceRoute::OPTION_NAMESPACE => $routeData,
253            ];
254        }
255        if (!isset($routeData['class']) || !is_subclass_of($routeData['class'], Route::class)) {
256            throw new \common_exception_InconsistentData('Invalid route ' . $routeId);
257        }
258        $className = $routeData['class'];
259        return new $className($extension, trim($routeId, '/'), $routeData);
260    }
261}