Completed
Push — master ( e0250b...fc23d8 )
by Tomas
02:44
created

ApiPresenter   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 211
Duplicated Lines 4.74 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 45.26%

Importance

Changes 10
Bugs 0 Features 3
Metric Value
wmc 26
c 10
b 0
f 3
lcom 2
cbo 13
dl 10
loc 211
ccs 43
cts 95
cp 0.4526
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A startup() 0 5 1
A setCorsHeader() 0 4 1
B renderDefault() 0 38 5
A getHandler() 0 9 1
A checkAuth() 5 9 2
A processParams() 5 10 2
A logRequest() 0 22 3
B sendCorsHeaders() 0 20 5
B getRequestDomain() 0 14 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Tomaj\NetteApi\Presenters;
4
5
use Nette\Application\Responses\JsonResponse;
6
use Nette\Application\UI\Presenter;
7
use Nette\Http\Response;
8
use Tomaj\NetteApi\ApiDecider;
9
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
10
use Tomaj\NetteApi\Handlers\ApiHandlerInterface;
11
use Tomaj\NetteApi\Logger\ApiLoggerInterface;
12
use Tomaj\NetteApi\Misc\IpDetectorInterface;
13
use Tomaj\NetteApi\Params\ParamsProcessor;
14
use Tomaj\NetteApi\Response\JsonApiResponse;
15
use Tracy\Debugger;
16
use Exception;
17
18
/**
19
 * @property-read \Nette\DI\Container $context
20
 */
21
class ApiPresenter extends Presenter
22
{
23
    /**
24
     * @var  ApiDecider @inject
25
     */
26
    public $apiDecider;
27
28
    /**
29
     * @var  IpDetectorInterface @inject
30
     */
31
    public $ipDetector;
32
33
    /**
34
     * CORS header settings
35
     *
36
     * Available values:
37
     *   'auto'  - send back header Access-Control-Allow-Origin with domain that made request
38
     *   '*'     - send header with '*' - this will workf fine if you dont need to send cookies via ajax calls to api
39
     *             with jquery $.ajax with xhrFields: { withCredentials: true } settings
40
     *   'off'   - will not send any CORS header
41
     *   other   - any other value will be send in Access-Control-Allow-Origin header
42
     *
43
     * @var string
44
     */
45
    protected $corsHeader = '*';
46
47
    /**
48
     * Presenter startup method
49
     *
50
     * @return void
51
     */
52 9
    public function startup()
53
    {
54 9
        parent::startup();
55 9
        $this->autoCanonicalize = false;
56 9
    }
57
58
    /**
59
     * Set cors header
60
     *
61
     * See description to property $corsHeader for valid inputs
62
     *
63
     * @param string $corsHeader
64
     */
65
    public function setCorsHeader($corsHeader)
66
    {
67
        $this->corsHeader = $corsHeader;
68
    }
69
70
    /**
71
     * Nette render default method
72
     *
73
     * @return void
74
     */
75 9
    public function renderDefault()
76
    {
77 9
        $start = microtime(true);
78
79 9
        $this->sendCorsHeaders();
80
81 9
        $hand = $this->getHandler();
82 9
        $handler = $hand['handler'];
83 9
        $authorization = $hand['authorization'];
84
85 9
        if ($this->checkAuth($authorization) === false) {
86
            return;
87
        }
88
89 6
        $params = $this->processParams($handler);
90 3
        if ($params === false) {
91
            return;
92
        }
93
94
        try {
95 3
            $response = $handler->handle($params);
96 3
            $code = $response->getCode();
97 3
        } catch (Exception $exception) {
98
            $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
99
            $code = $response->getCode();
100
            Debugger::log($exception, Debugger::EXCEPTION);
101
        }
102
103 3
        $end = microtime(true);
104
105 3
        if ($this->context->hasService('apiLogger')) {
106
            $this->logRequest($this->context->getService('apiLogger'), $code, $end - $start);
107
        }
108
109
        // output to nette
110 3
        $this->getHttpResponse()->setCode($code);
111 3
        $this->sendResponse($response);
112
    }
113
114
    /**
115
     * Get handler information triplet (endpoint, handler, authorization)
116
     *
117
     * @return array
118
     */
119 9
    private function getHandler()
120
    {
121 9
        return $this->apiDecider->getApiHandler(
122 9
            $this->getRequest()->getMethod(),
123 9
            $this->params['version'],
124 9
            $this->params['package'],
125 9
            $this->params['apiAction']
126 9
        );
127
    }
128
129
    /**
130
     * Check authorization
131
     *
132
     * @param ApiAuthorizationInterface  $authorization
133
     *
134
     * @return bool
135
     */
136 9
    private function checkAuth(ApiAuthorizationInterface $authorization)
137
    {
138 9 View Code Duplication
        if (!$authorization->authorized()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139 3
            $this->getHttpResponse()->setCode(Response::S403_FORBIDDEN);
140 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]));
141
            return false;
142
        }
143 6
        return true;
144
    }
145
146
    /**
147
     * Process input parameters
148
     *
149
     * @param ApiHandlerInterface   $handler
150
     *
151
     * @return array|bool
152
     */
153 6
    private function processParams(ApiHandlerInterface $handler)
154
    {
155 6
        $paramsProcessor = new ParamsProcessor($handler->params());
156 6 View Code Duplication
        if ($paramsProcessor->isError()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
157 3
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
158 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
159
            return false;
160
        }
161 3
        return $paramsProcessor->getValues();
162
    }
163
164
    /**
165
     * Log request
166
     *
167
     * @param ApiLoggerInterface  $logger
168
     * @param integer             $code
169
     * @param double              $elapsed
170
     *
171
     * @return void
172
     */
173
    private function logRequest(ApiLoggerInterface $logger, $code, $elapsed)
174
    {
175
        $headers = [];
176
        if (function_exists('getallheaders')) {
177
            $headers = getallheaders();
178
        }
179
180
        $requestHeaders = '';
181
        foreach ($headers as $key => $value) {
182
            $requestHeaders .= "$key: $value\n";
183
        }
184
185
        $logger->log(
186
            $code,
187
            $this->getRequest()->getMethod(),
188
            $requestHeaders,
189
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
190
            $this->ipDetector->getRequestIp(),
191
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
192
            ($elapsed) * 1000
193
        );
194
    }
195
196 9
    protected function sendCorsHeaders()
197
    {
198 9
        if ($this->corsHeader == 'auto') {
199
            $domain = $this->getRequestDomain();
200
            if ($domain !== false) {
201
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
202
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
203
            }
204
            return;
205
        }
206
207 9
        if ($this->corsHeader == '*') {
208 9
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
209 9
            return;
210
        }
211
212
        if ($this->corsHeader != 'off') {
213
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
214
        }
215
    }
216
217
    private function getRequestDomain()
218
    {
219
        if (filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
220
            $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
221
            if (isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host'])) {
222
                $url = $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
223
                if (isset($refererParsedUrl['port']) && $refererParsedUrl['port'] !== 80) {
224
                    $url .= ':' . $refererParsedUrl['port'];
225
                }
226
                return $url;
227
            }
228
        }
229
        return false;
230
    }
231
}
232