Completed
Push — master ( f71e62...3bdf74 )
by Tomas
02:54
created

ApiPresenter::getHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.6666
cc 1
eloc 6
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Tomaj\NetteApi\Presenters;
4
5
use Exception;
6
use Nette\Application\Responses\JsonResponse;
7
use Nette\Application\UI\Presenter;
8
use Nette\DI\Container;
9
use Nette\Http\Response;
10
use Tomaj\NetteApi\ApiDecider;
11
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
12
use Tomaj\NetteApi\Handlers\ApiHandlerInterface;
13
use Tomaj\NetteApi\Logger\ApiLoggerInterface;
14
use Tomaj\NetteApi\Params\ParamsProcessor;
15
use Tomaj\NetteApi\Response\JsonApiResponse;
16
use Tracy\Debugger;
17
18
/**
19
 * @property-read Container $context
20
 */
21
class ApiPresenter extends Presenter
22
{
23
    /**
24
     * @var  ApiDecider @inject
25
     */
26
    public $apiDecider;
27
28
    /**
29
     * CORS header settings
30
     *
31
     * Available values:
32
     *   'auto'  - send back header Access-Control-Allow-Origin with domain that made request
33
     *   '*'     - send header with '*' - this will workf fine if you dont need to send cookies via ajax calls to api
34
     *             with jquery $.ajax with xhrFields: { withCredentials: true } settings
35
     *   'off'   - will not send any CORS header
36
     *   other   - any other value will be send in Access-Control-Allow-Origin header
37
     *
38
     * @var string
39
     */
40
    protected $corsHeader = '*';
41
42
    /**
43
     * Presenter startup method
44
     *
45
     * @return void
46
     */
47 9
    public function startup()
48
    {
49 9
        parent::startup();
50 9
        $this->autoCanonicalize = false;
51 9
    }
52
53
    /**
54
     * Set cors header
55
     *
56
     * See description to property $corsHeader for valid inputs
57
     *
58
     * @param string $corsHeader
59
     */
60
    public function setCorsHeader($corsHeader)
61
    {
62
        $this->corsHeader = $corsHeader;
63
    }
64
65
    /**
66
     * Nette render default method
67
     *
68
     * @return void
69
     */
70 9
    public function renderDefault()
71
    {
72 9
        $start = microtime(true);
73
74 9
        $this->sendCorsHeaders();
75
76 9
        $hand = $this->getHandler();
77 9
        $handler = $hand['handler'];
78 9
        $authorization = $hand['authorization'];
79
80 9
        if ($this->checkAuth($authorization) === false) {
81
            return;
82
        }
83
84 6
        $params = $this->processParams($handler);
85 3
        if ($params === false) {
86
            return;
87
        }
88
89
        try {
90 3
            $response = $handler->handle($params);
91 3
            $code = $response->getCode();
92 1
        } catch (Exception $exception) {
93
            $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
94
            $code = $response->getCode();
95
            Debugger::log($exception, Debugger::EXCEPTION);
96
        }
97
98 3
        $end = microtime(true);
99
100 3
        if ($this->context->findByType('Tomaj\NetteApi\Logger\ApiLoggerInterface')) {
101
            $this->logRequest($this->context->getByType('Tomaj\NetteApi\Logger\ApiLoggerInterface'), $code, $end - $start);
0 ignored issues
show
Documentation introduced by
$this->context->getByTyp...r\\ApiLoggerInterface') is of type object|null, but the function expects a object<Tomaj\NetteApi\Logger\ApiLoggerInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
102
        }
103
104
        // output to nette
105 3
        $this->getHttpResponse()->setCode($code);
106 3
        $this->sendResponse($response);
107
    }
108
109
    /**
110
     * Get handler information triplet (endpoint, handler, authorization)
111
     *
112
     * @return array
113
     */
114 9
    private function getHandler()
115
    {
116 9
        return $this->apiDecider->getApiHandler(
117 9
            $this->getRequest()->getMethod(),
118 9
            $this->params['version'],
119 9
            $this->params['package'],
120 9
            $this->params['apiAction']
121 3
        );
122
    }
123
124
    /**
125
     * Check authorization
126
     *
127
     * @param ApiAuthorizationInterface  $authorization
128
     *
129
     * @return bool
130
     */
131 9
    private function checkAuth(ApiAuthorizationInterface $authorization)
132
    {
133 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...
134 3
            $this->getHttpResponse()->setCode(Response::S403_FORBIDDEN);
135 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]));
136
            return false;
137
        }
138 6
        return true;
139
    }
140
141
    /**
142
     * Process input parameters
143
     *
144
     * @param ApiHandlerInterface   $handler
145
     *
146
     * @return array|bool
147
     */
148 6
    private function processParams(ApiHandlerInterface $handler)
149
    {
150 6
        $paramsProcessor = new ParamsProcessor($handler->params());
151 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...
152 3
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
153 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
154
            return false;
155
        }
156 3
        return $paramsProcessor->getValues();
157
    }
158
159
    /**
160
     * Log request
161
     *
162
     * @param ApiLoggerInterface  $logger
163
     * @param integer             $code
164
     * @param double              $elapsed
165
     *
166
     * @return void
167
     */
168
    private function logRequest(ApiLoggerInterface $logger, $code, $elapsed)
169
    {
170
        $headers = [];
171
        if (function_exists('getallheaders')) {
172
            $headers = getallheaders();
173
        }
174
175
        $requestHeaders = '';
176
        foreach ($headers as $key => $value) {
177
            $requestHeaders .= "$key: $value\n";
178
        }
179
180
        $ipDetector = $this->context->getByType('Tomaj\NetteApi\Misc\IpDetectorInterface');
181
        $logger->log(
182
            $code,
183
            $this->getRequest()->getMethod(),
184
            $requestHeaders,
185
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
186
            $ipDetector->getRequestIp(),
187
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
188
            ($elapsed) * 1000
189
        );
190
    }
191
192 9
    protected function sendCorsHeaders()
193
    {
194 9
        $this->getHttpResponse()->addHeader('Access-Control-Allow-Headers', 'Content-Type');
195 9
        $this->getHttpResponse()->addHeader('Access-Control-Allow-Methods', 'POST, DELETE, PUT, GET, OPTIONS');
196
197 9
        if ($this->corsHeader == 'auto') {
198
            $domain = $this->getRequestDomain();
199
            if ($domain !== false) {
200
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
201
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
202
            }
203
            return;
204
        }
205
206 9
        if ($this->corsHeader == '*') {
207 9
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
208 9
            return;
209
        }
210
211
        if ($this->corsHeader != 'off') {
212
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
213
        }
214
    }
215
216
    private function getRequestDomain()
217
    {
218
        if (!filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
219
            return false;
220
        }
221
        $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
222
        if (!(isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host']))) {
223
            return false;
224
        }
225
        $url = $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
226
        if (isset($refererParsedUrl['port']) && $refererParsedUrl['port'] !== 80) {
227
            $url .= ':' . $refererParsedUrl['port'];
228
        }
229
        return $url;
230
    }
231
}
232