Completed
Push — master ( 73f4b5...67fd66 )
by Tomas
07:31
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
            if (Debugger::isEnabled()) {
94
                $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]);
95
            } else {
96
                $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
97
            }
98
            $code = $response->getCode();
99
            Debugger::log($exception, Debugger::EXCEPTION);
100
        }
101
102 3
        $end = microtime(true);
103
104 3
        if ($this->context->findByType('Tomaj\NetteApi\Logger\ApiLoggerInterface')) {
105
            $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...
106
        }
107
108
        // output to nette
109 3
        $this->getHttpResponse()->setCode($code);
110 3
        $this->sendResponse($response);
111
    }
112
113
    /**
114
     * Get handler information triplet (endpoint, handler, authorization)
115
     *
116
     * @return array
117
     */
118 9
    private function getHandler()
119
    {
120 9
        return $this->apiDecider->getApiHandler(
121 9
            $this->getRequest()->getMethod(),
122 9
            $this->params['version'],
123 9
            $this->params['package'],
124 9
            $this->params['apiAction']
125 3
        );
126
    }
127
128
    /**
129
     * Check authorization
130
     *
131
     * @param ApiAuthorizationInterface  $authorization
132
     *
133
     * @return bool
134
     */
135 9
    private function checkAuth(ApiAuthorizationInterface $authorization)
136
    {
137 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...
138 3
            $this->getHttpResponse()->setCode(Response::S403_FORBIDDEN);
139 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]));
140
            return false;
141
        }
142 6
        return true;
143
    }
144
145
    /**
146
     * Process input parameters
147
     *
148
     * @param ApiHandlerInterface   $handler
149
     *
150
     * @return array|bool
151
     */
152 6
    private function processParams(ApiHandlerInterface $handler)
153
    {
154 6
        $paramsProcessor = new ParamsProcessor($handler->params());
155 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...
156 3
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
157 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
158
            return false;
159
        }
160 3
        return $paramsProcessor->getValues();
161
    }
162
163
    /**
164
     * Log request
165
     *
166
     * @param ApiLoggerInterface  $logger
167
     * @param integer             $code
168
     * @param double              $elapsed
169
     *
170
     * @return void
171
     */
172
    private function logRequest(ApiLoggerInterface $logger, $code, $elapsed)
0 ignored issues
show
Coding Style introduced by
logRequest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
173
    {
174
        $headers = [];
175
        if (function_exists('getallheaders')) {
176
            $headers = getallheaders();
177
        } elseif (isset($_SERVER)) {
178
            foreach ($_SERVER as $name => $value) {
179
                if (substr($name, 0, 5) == 'HTTP_') {
180
                    $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
181
                    $headers[$key] = $value;
182
                }
183
            }
184
        }
185
186
        $requestHeaders = '';
187
        foreach ($headers as $key => $value) {
188
            $requestHeaders .= "$key: $value\n";
189
        }
190
191
        $ipDetector = $this->context->getByType('Tomaj\NetteApi\Misc\IpDetectorInterface');
192
        $logger->log(
193
            $code,
194
            $this->getRequest()->getMethod(),
195
            $requestHeaders,
196
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
197
            $ipDetector->getRequestIp(),
198
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
199
            ($elapsed) * 1000
200
        );
201
    }
202
203 9
    protected function sendCorsHeaders()
204
    {
205 9
        $this->getHttpResponse()->addHeader('Access-Control-Allow-Methods', 'POST, DELETE, PUT, GET, OPTIONS');
206
207 9
        if ($this->corsHeader == 'auto') {
208
            $domain = $this->getRequestDomain();
209
            if ($domain !== false) {
210
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
211
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
212
            }
213
            return;
214
        }
215
216 9
        if ($this->corsHeader == '*') {
217 9
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
218 9
            return;
219
        }
220
221 1
        if ($this->corsHeader != 'off') {
222
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
223
        }
224
    }
225
226
    private function getRequestDomain()
227
    {
228
        if (!filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
229
            return false;
230
        }
231
        $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
232
        if (!(isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host']))) {
233
            return false;
234
        }
235
        $url = $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
236
        if (isset($refererParsedUrl['port']) && $refererParsedUrl['port'] !== 80) {
237
            $url .= ':' . $refererParsedUrl['port'];
238
        }
239
        return $url;
240
    }
241
}
242