Passed
Push — develop ( fceade...14940b )
by Pablo
09:35
created

ApiRouter::outputGetFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Routes requests to the API to the appropriate controllers
5
 *
6
 * @package     Nails
7
 * @subpackage  module-api
8
 * @category    Controller
9
 * @author      Nails Dev Team
10
 * @link
11
 */
12
13
use Nails\Auth;
14
use Nails\Api\Constants;
15
use Nails\Api\Exception\ApiException;
16
use Nails\Api\Factory\ApiResponse;
17
use Nails\Common\Exception\NailsException;
18
use Nails\Common\Exception\ValidationException;
19
use Nails\Common\Factory\HttpRequest;
0 ignored issues
show
Bug introduced by Pablo de la Peña
This use statement conflicts with another class in this namespace, HttpRequest. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
20
use Nails\Common\Factory\Logger;
21
use Nails\Common\Service\HttpCodes;
22
use Nails\Common\Service\Input;
23
use Nails\Common\Service\Output;
24
use Nails\Components;
25
use Nails\Environment;
26
use Nails\Factory;
27
28
// --------------------------------------------------------------------------
29
30
/**
31
 * Allow the app to add functionality, if needed
32
 */
33
if (class_exists('\App\Api\Controller\BaseRouter')) {
34
    abstract class BaseMiddle extends \App\Api\Controller\BaseRouter
0 ignored issues
show
Bug introduced by Pablo de la Peña
The type App\Api\Controller\BaseRouter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
35
    {
36
    }
37
} else {
38
    abstract class BaseMiddle extends \Nails\Common\Controller\Base
39
    {
40
    }
41
}
42
43
// --------------------------------------------------------------------------
44
45
/**
46
 * Class ApiRouter
47
 */
48
class ApiRouter extends BaseMiddle
49
{
50
    const DEFAULT_FORMAT                   = \Nails\Api\Api\Output\Json::SLUG;
51
    const REQUEST_METHOD_GET               = HttpRequest\Get::HTTP_METHOD;
52
    const REQUEST_METHOD_PUT               = HttpRequest\Put::HTTP_METHOD;
53
    const REQUEST_METHOD_POST              = HttpRequest\Post::HTTP_METHOD;
54
    const REQUEST_METHOD_DELETE            = HttpRequest\Delete::HTTP_METHOD;
55
    const REQUEST_METHOD_OPTIONS           = HttpRequest\Options::HTTP_METHOD;
56
    const ACCESS_TOKEN_HEADER              = 'X-Access-Token';
57
    const ACCESS_TOKEN_POST_PARAM          = 'accessToken';
58
    const ACCESS_TOKEN_GET_PARAM           = 'accessToken';
59
    const ACCESS_CONTROL_ALLOW_ORIGIN      = '*';
60
    const ACCESS_CONTROL_ALLOW_CREDENTIALS = 'true';
61
    const ACCESS_CONTROL_MAX_AGE           = 86400;
62
    const ACCESS_CONTROL_ALLOW_HEADERS     = ['*'];
63
    const ACCESS_CONTROL_ALLOW_METHODS     = [
64
        self::REQUEST_METHOD_GET,
65
        self::REQUEST_METHOD_PUT,
66
        self::REQUEST_METHOD_POST,
67
        self::REQUEST_METHOD_DELETE,
68
        self::REQUEST_METHOD_OPTIONS,
69
    ];
70
    const OUTPUT_FORMAT_PATTERN            = '/\.([a-z]*)$/';
71
72
    // --------------------------------------------------------------------------
73
74
    /** @var array */
75
    protected static $aOutputValidFormats;
76
77
    // --------------------------------------------------------------------------
78
79
    /** @var string */
80
    private $sRequestMethod;
81
82
    /** @var string */
83
    private $sModuleName;
84
85
    /** @var string */
86
    private $sClassName;
87
88
    /** @var string */
89
    private $sMethod;
90
91
    /** @var string */
92
    private $sOutputFormat;
93
94
    /** @var bool */
95
    private $bOutputSendHeader = true;
96
97
    /** @var Logger */
98
    private $oLogger;
99
100
    /** @var string */
101
    private $sAccessToken;
102
103
    // --------------------------------------------------------------------------
104
105
    /**
106
     * ApiRouter constructor.
107
     *
108
     * @throws \Nails\Common\Exception\FactoryException
109
     */
110
    public function __construct()
111
    {
112
        parent::__construct();
113
114
        // --------------------------------------------------------------------------
115
116
        //  Work out the request method
117
        /** @var Input $oInput */
118
        $oInput               = Factory::service('Input');
119
        $this->sRequestMethod = $oInput->server('REQUEST_METHOD');
120
        $this->sRequestMethod = $this->sRequestMethod ? $this->sRequestMethod : static::REQUEST_METHOD_GET;
121
122
        /**
123
         * In order to work out the next few parts we'll analyse the URI string manually.
124
         * We're doing this because of the optional return type at the end of the string;
125
         * it's easier to regex that quickly, remove it, then split up the segments.
126
         */
127
128
        //  Look for valid output formats
129
        $aComponents = Components::available();
130
131
        //  Shift the app onto the end so it overrides any module supplied formats
132
        $oApp = array_shift($aComponents);
133
        array_push($aComponents, $oApp);
134
135
        foreach ($aComponents as $oComponent) {
136
137
            $oClasses = $oComponent
138
                ->findClasses('Api\Output')
139
                ->whichImplement(\Nails\Api\Interfaces\Output::class);
140
141
            foreach ($oClasses as $sClass) {
142
                static::$aOutputValidFormats[strtoupper($sClass::getSlug())] = $sClass;
143
            }
144
        }
145
146
        $this->sOutputFormat = static::detectOutputFormat();
147
        $sUri                = preg_replace(static::OUTPUT_FORMAT_PATTERN, '', uri_string());
148
149
        //  Remove the module prefix (i.e "api/") then explode into segments
150
        //  Using regex as some systems will report a leading slash (e.g CLI)
151
        $sUri = preg_replace('#/?api/#', '', $sUri);
152
        $aUri = explode('/', $sUri);
153
154
        //  Work out the sModuleName, sClassName and method
155
        $this->sModuleName = getFromArray(0, $aUri, null);
156
        $this->sClassName  = ucfirst(getFromArray(1, $aUri, $this->sModuleName));
157
        $this->sMethod     = getFromArray(2, $aUri, 'index');
158
159
        //  Configure logging
160
        /** @var \Nails\Common\Resource\DateTime $oNow */
161
        $oNow = Factory::factory('DateTime');
162
        /** @var Logger oLogger */
163
        $this->oLogger = Factory::factory('Logger');
164
        $this->oLogger->setFile('api-' . $oNow->format('Y-m-d') . '.php');
165
    }
166
167
    // --------------------------------------------------------------------------
168
169
    /**
170
     * Route the call to the correct place
171
     */
172
    public function index()
173
    {
174
        //  Handle OPTIONS CORS pre-flight requests
175
        if ($this->sRequestMethod === static::REQUEST_METHOD_OPTIONS) {
176
177
            $this->setCorsHeaders();
178
            /** @var Output $oOutput */
179
            $oOutput = Factory::service('Output');
180
            $oOutput->setStatusHeader(HttpCodes::STATUS_NO_CONTENT);
181
            return;
182
183
        } else {
184
185
            try {
186
                /**
187
                 * If an access token has been passed then verify it
188
                 *
189
                 * Passing the token via the header is preferred, but fallback to the GET
190
                 * and POST arrays.
191
                 */
192
193
                /** @var Input $oInput */
194
                $oInput = Factory::service('Input');
195
                /** @var HttpCodes $oHttpCodes */
196
                $oHttpCodes = Factory::service('HttpCodes');
197
                /** @var Auth\Model\User\AccessToken $oUserAccessTokenModel */
198
                $oUserAccessTokenModel = Factory::model('UserAccessToken', Auth\Constants::MODULE_SLUG);
199
200
                $sAccessToken = $oInput->header(static::ACCESS_TOKEN_HEADER);
201
202
                if (!$sAccessToken) {
203
                    $sAccessToken = $oInput->post(static::ACCESS_TOKEN_POST_PARAM);
204
                }
205
206
                if (!$sAccessToken) {
207
                    $sAccessToken = $oInput->get(static::ACCESS_TOKEN_GET_PARAM);
208
                }
209
210
                if ($sAccessToken) {
211
212
                    $this->sAccessToken = $sAccessToken;
0 ignored issues
show
Documentation Bug introduced by Pablo de la Peña
It seems like $sAccessToken can also be of type array. However, the property $sAccessToken is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
213
                    $oAccessToken       = $oUserAccessTokenModel->getByValidToken($sAccessToken);
0 ignored issues
show
Bug introduced by Pablo de la Peña
It seems like $sAccessToken can also be of type array; however, parameter $sToken of Nails\Auth\Model\User\Ac...oken::getByValidToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
                    $oAccessToken       = $oUserAccessTokenModel->getByValidToken(/** @scrutinizer ignore-type */ $sAccessToken);
Loading history...
214
215
                    if ($oAccessToken) {
0 ignored issues
show
introduced by Pablo de la Peña
$oAccessToken is of type Nails\Common\Resource, thus it always evaluated to true.
Loading history...
216
                        /** @var Auth\Model\User $oUserModel */
217
                        $oUserModel = Factory::model('User', Auth\Constants::MODULE_SLUG);
218
                        $oUserModel->setLoginData($oAccessToken->user_id, false);
0 ignored issues
show
Bug introduced by Pablo de la Peña
The property user_id does not seem to exist on Nails\Common\Resource.
Loading history...
219
                    } else {
220
                        throw new ApiException(
221
                            'Invalid access token',
222
                            $oHttpCodes::STATUS_UNAUTHORIZED
223
                        );
224
                    }
225
                }
226
227
                // --------------------------------------------------------------------------
228
229
                if (!$this->outputSetFormat($this->sOutputFormat)) {
230
                    throw new ApiException(
231
                        '"' . $this->sOutputFormat . '" is not a valid format.',
232
                        $oHttpCodes::STATUS_BAD_REQUEST
233
                    );
234
                }
235
236
                // --------------------------------------------------------------------------
237
238
                //  Register API modules
239
                $aNamespaces = [
240
                    'app' => (object) [
241
                        'namespace' => 'App\\',
242
                    ],
243
                ];
244
                foreach (Components::modules() as $oModule) {
245
                    if (!empty($oModule->data->{Constants::MODULE_SLUG}->namespace)) {
246
                        $sNamespace = $oModule->data->{Constants::MODULE_SLUG}->namespace;
247
                        if (array_key_exists($sNamespace, $aNamespaces)) {
248
                            throw new NailsException(
249
                                sprintf(
250
                                    'Conflicting API namespace "%s" in use by "%s" and "%s"',
251
                                    $sNamespace,
252
                                    $oModule->slug,
253
                                    $aNamespaces[$sNamespace]->slug
254
                                )
255
                            );
256
                        }
257
                        $aNamespaces[$sNamespace] = $oModule;
258
                    }
259
                }
260
261
                $i404Status = $oHttpCodes::STATUS_NOT_FOUND;
262
                $s404Error  = sprintf(
263
                    '"%s: %s/%s/%s" is not a valid API route.',
264
                    $this->getRequestMethod(),
265
                    strtolower($this->sModuleName),
266
                    strtolower($this->sClassName),
267
                    strtolower($this->sMethod)
268
                );
269
270
                if (!array_key_exists($this->sModuleName, $aNamespaces)) {
271
                    throw new ApiException($s404Error, $i404Status);
272
                }
273
274
                $oNamespace          = $aNamespaces[$this->sModuleName];
275
                $sOriginalController = $this->sClassName;
276
277
                //  Do we need to remap the controller?
278
                if (!empty($oNamespace->data->{Constants::MODULE_SLUG}->{'controller-map'})) {
279
280
                    $aMap             = (array) $oNamespace->data->{Constants::MODULE_SLUG}->{'controller-map'};
281
                    $this->sClassName = getFromArray($this->sClassName, $aMap, $this->sClassName);
282
283
                    //  This prevents users from accessing the "correct" controller, so we only have one valid route
284
                    $sRemapped = array_search($sOriginalController, $aMap);
285
                    if ($sRemapped !== false) {
286
                        $this->sClassName = $sRemapped;
287
                    }
288
                }
289
290
                $sController = $oNamespace->namespace . 'Api\\Controller\\' . $this->sClassName;
291
292
                if (!class_exists($sController)) {
293
                    throw new ApiException($s404Error, $i404Status);
294
                }
295
296
                $mAuth = $sController::isAuthenticated($this->sRequestMethod, $this->sMethod);
297
                if ($mAuth !== true) {
298
299
                    if (is_array($mAuth)) {
300
                        $sError  = getFromArray('error', $mAuth, $oHttpCodes::getByCode($oHttpCodes::STATUS_UNAUTHORIZED));
0 ignored issues
show
Bug introduced by Pablo de la Peña
Are you sure the usage of $oHttpCodes::getByCode($...s::STATUS_UNAUTHORIZED) targeting Nails\Common\Service\HttpCodes::getByCode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
301
                        $iStatus = (int) getFromArray('status', $mAuth, $oHttpCodes::STATUS_UNAUTHORIZED);
302
                    } else {
303
                        $sError  = 'You must be logged in to access this resource';
304
                        $iStatus = $oHttpCodes::STATUS_UNAUTHORIZED;
305
                    }
306
307
                    throw new ApiException($sError, $iStatus);
308
                }
309
310
                if (!empty($sController::REQUIRE_SCOPE)) {
311
                    if (!$oUserAccessTokenModel->hasScope($oAccessToken, $sController::REQUIRE_SCOPE)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by Pablo de la Peña
The variable $oAccessToken does not seem to be defined for all execution paths leading up to this point.
Loading history...
312
                        throw new ApiException(
313
                            'Access token with "' . $sController::REQUIRE_SCOPE . '" scope is required.',
314
                            $oHttpCodes::STATUS_UNAUTHORIZED
315
                        );
316
                    }
317
                }
318
319
                //  New instance of the controller
320
                $oInstance = new $sController($this);
321
322
                /**
323
                 * We need to look for the appropriate method; we'll look in the following order:
324
                 *
325
                 * - {sRequestMethod}Remap()
326
                 * - {sRequestMethod}{method}()
327
                 * - anyRemap()
328
                 * - any{method}()
329
                 *
330
                 * The second parameter is whether the method is a remap method or not.
331
                 */
332
                $bDidFindRoute = false;
333
                $aMethods      = [
334
                    [
335
                        strtolower($this->sRequestMethod) . 'Remap',
336
                        true,
337
                    ],
338
                    [
339
                        strtolower($this->sRequestMethod) . ucfirst($this->sMethod),
340
                        false,
341
                    ],
342
                    [
343
                        'anyRemap',
344
                        true,
345
                    ],
346
                    [
347
                        'any' . ucfirst($this->sMethod),
348
                        false,
349
                    ],
350
                ];
351
352
                foreach ($aMethods as $aMethodName) {
353
354
                    $sMethod  = getFromArray(0, $aMethodName);
355
                    $bIsRemap = (bool) getFromArray(1, $aMethodName);
356
                    if (is_callable([$oInstance, $sMethod])) {
357
358
                        $bDidFindRoute = true;
359
360
                        /**
361
                         * If the method we're trying to call is a remap method, then the first
362
                         * param should be the name of the method being called
363
                         */
364
                        if ($bIsRemap) {
365
                            $oResponse = call_user_func_array([$oInstance, $sMethod], [$this->sMethod]);
366
                        } else {
367
                            $oResponse = call_user_func([$oInstance, $sMethod]);
368
                        }
369
                        break;
370
                    }
371
                }
372
373
                if (!$bDidFindRoute) {
374
                    throw new ApiException(
375
                        $s404Error,
376
                        $i404Status
377
                    );
378
                }
379
380
                if (!($oResponse instanceof ApiResponse)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by Pablo de la Peña
The variable $oResponse does not seem to be defined for all execution paths leading up to this point.
Loading history...
381
                    //  This is a misconfiguration error, which we want to bubble up to the error handler
382
                    throw new NailsException(
383
                        'Return object must be an instance of \Nails\Api\Factory\ApiResponse',
384
                        $oHttpCodes::STATUS_INTERNAL_SERVER_ERROR
385
                    );
386
                }
387
388
                $aOut = [
389
                    'status' => $oResponse->getCode(),
390
                    'body'   => $oResponse->getBody(),
391
                    'data'   => $oResponse->getData(),
392
                    'meta'   => $oResponse->getMeta(),
393
                ];
394
395
            } catch (ValidationException $e) {
396
397
                $aOut = [
398
                    'status'  => $e->getCode() ?: $oHttpCodes::STATUS_BAD_REQUEST,
0 ignored issues
show
Comprehensibility Best Practice introduced by Pablo de la Peña
The variable $oHttpCodes does not seem to be defined for all execution paths leading up to this point.
Loading history...
399
                    'error'   => $e->getMessage() ?: 'An unkown validation error occurred',
400
                    'details' => $e->getData() ?: [],
401
                ];
402
                if (isSuperuser()) {
403
                    $aOut['exception'] = (object) array_filter([
404
                        'type' => get_class($e),
405
                        'file' => $e->getFile(),
406
                        'line' => $e->getLine(),
407
                    ]);
408
                }
409
410
                $this->writeLog($aOut);
411
412
            } catch (ApiException $e) {
413
414
                $aOut = [
415
                    'status'  => $e->getCode() ?: $oHttpCodes::STATUS_INTERNAL_SERVER_ERROR,
416
                    'error'   => $e->getMessage() ?: 'An unkown error occurred',
417
                    'details' => $e->getData() ?: [],
418
                ];
419
                if (isSuperuser()) {
420
                    $aOut['exception'] = (object) array_filter([
421
                        'type' => get_class($e),
422
                        'file' => $e->getFile(),
423
                        'line' => $e->getLine(),
424
                    ]);
425
                }
426
427
                $this->writeLog($aOut);
428
429
            } catch (\Exception $e) {
430
                /**
431
                 * When running in PRODUCTION we want the global error handler to catch exceptions so that they
432
                 * can be handled proeprly and reported if necessary. In other environments we want to show the
433
                 * developer the error quickly and with as much info as possible.
434
                 */
435
                if (Environment::is(Environment::ENV_PROD)) {
436
                    throw $e;
437
                } else {
438
                    $aOut = [
439
                        'status'    => $e->getCode() ?: $oHttpCodes::STATUS_INTERNAL_SERVER_ERROR,
440
                        'error'     => $e->getMessage() ?: 'An unkown error occurred',
441
                        'exception' => (object) array_filter([
442
                            'type' => get_class($e),
443
                            'file' => $e->getFile(),
444
                            'line' => $e->getLine(),
445
                        ]),
446
                    ];
447
448
                    $this->writeLog($aOut);
449
                }
450
            }
451
452
            $this->output($aOut);
453
        }
454
    }
455
456
    // --------------------------------------------------------------------------
457
458
    /**
459
     * Sends $aOut to the browser in the desired format
460
     *
461
     * @param array $aOut The data to output to the browser
462
     */
463
    protected function output($aOut = [])
464
    {
465
        /** @var Input $oInput */
466
        $oInput = Factory::service('Input');
467
        /** @var Output $oOutput */
468
        $oOutput = Factory::service('Output');
469
        /** @var HttpCodes $oHttpCodes */
470
        $oHttpCodes = Factory::service('HttpCodes');
471
472
        //  Set cache headers
473
        $oOutput
474
            ->setHeader('Cache-Control: no-store, no-cache, must-revalidate')
475
            ->setHeader('Expires: Mon, 26 Jul 1997 05:00:00 GMT')
476
            ->setHeader('Pragma: no-cache');
477
478
        //  Set access control headers
479
        $this->setCorsHeaders();
480
481
        // --------------------------------------------------------------------------
482
483
        //  Send the correct status header, default to 200 OK
484
        if ($this->bOutputSendHeader) {
485
            $sProtocol   = $oInput->server('SERVER_PROTOCOL');
486
            $iHttpCode   = getFromArray('status', $aOut, $oHttpCodes::STATUS_OK);
487
            $sHttpString = $oHttpCodes::getByCode($iHttpCode);
0 ignored issues
show
Bug introduced by Pablo de la Peña
Are you sure the assignment to $sHttpString is correct as $oHttpCodes::getByCode($iHttpCode) targeting Nails\Common\Service\HttpCodes::getByCode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
488
            $oOutput->setHeader($sProtocol . ' ' . $iHttpCode . ' ' . $sHttpString);
0 ignored issues
show
Bug introduced by Pablo de la Peña
Are you sure $sProtocol of type array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

488
            $oOutput->setHeader(/** @scrutinizer ignore-type */ $sProtocol . ' ' . $iHttpCode . ' ' . $sHttpString);
Loading history...
489
        }
490
491
        // --------------------------------------------------------------------------
492
493
        //  Output content
494
        $sOutputClass = static::$aOutputValidFormats[$this->sOutputFormat];
495
        $oOutput->setContentType($sOutputClass::getContentType());
496
497
        if (array_key_exists('body', $aOut) && $aOut['body'] !== null) {
498
            $sOut = $aOut['body'];
499
        } else {
500
            unset($aOut['body']);
501
            $sOut = $sOutputClass::render($aOut);
502
        }
503
504
        $oOutput->setOutput($sOut);
505
    }
506
507
    // --------------------------------------------------------------------------
508
509
    /**
510
     * Sets CORS headers
511
     *
512
     * @throws \Nails\Common\Exception\FactoryException
513
     */
514
    protected function setCorsHeaders()
515
    {
516
        /** @var Output $oOutput */
517
        $oOutput = Factory::service('Output');
518
        $oOutput
519
            ->setHeader('Access-Control-Allow-Origin: ' . static::ACCESS_CONTROL_ALLOW_ORIGIN)
520
            ->setHeader('Access-Control-Allow-Credentials: ' . static::ACCESS_CONTROL_ALLOW_CREDENTIALS)
521
            ->setHeader('Access-Control-Allow-Headers: ' . implode(', ', static::ACCESS_CONTROL_ALLOW_HEADERS))
522
            ->setHeader('Access-Control-Allow-Methods: ' . implode(', ', static::ACCESS_CONTROL_ALLOW_METHODS))
523
            ->setHeader('Access-Control-Max-Age: ' . static::ACCESS_CONTROL_MAX_AGE);
524
    }
525
526
    // --------------------------------------------------------------------------
527
528
    /**
529
     * Sets the output format
530
     *
531
     * @param string $sFormat The format to use
532
     *
533
     * @return bool
534
     */
535
    public function outputSetFormat($sFormat): bool
536
    {
537
        if (static::isValidFormat($sFormat)) {
538
            $this->sOutputFormat = strtoupper($sFormat);
539
            return true;
540
        }
541
542
        return false;
543
    }
544
545
    // --------------------------------------------------------------------------
546
547
    /**
548
     * Returns the putput format
549
     *
550
     * @return string
551
     */
552
    public function outputGetFormat(): string
553
    {
554
        return $this->sOutputFormat;
555
    }
556
557
    // --------------------------------------------------------------------------
558
559
    /**
560
     * Detects the putput frmat from the URI
561
     *
562
     * @return string
563
     */
564
    public static function detectOutputFormat(): string
565
    {
566
        preg_match(static::OUTPUT_FORMAT_PATTERN, uri_string(), $aMatches);
567
        $sFormat = !empty($aMatches[1]) ? strtoupper($aMatches[1]) : null;
568
569
        return static::isValidFormat($sFormat)
570
            ? $sFormat
571
            : static::DEFAULT_FORMAT;
572
    }
573
574
    // --------------------------------------------------------------------------
575
576
    /**
577
     * Sets whether the status header should be sent or not
578
     *
579
     * @param bool $sendHeader Whether the header should be sent or not
580
     */
581
    public function outputSendHeader($sendHeader): bool
582
    {
583
        $this->bOutputSendHeader = !empty($sendHeader);
584
    }
585
586
    // --------------------------------------------------------------------------
587
588
    /**
589
     * Determines whether the format is valid
590
     *
591
     * @param string $sFormat The format to check
592
     *
593
     * @return bool
594
     */
595
    private static function isValidFormat(?string $sFormat): bool
596
    {
597
        return in_array(strtoupper($sFormat), array_keys(static::$aOutputValidFormats));
0 ignored issues
show
Bug introduced by Pablo de la Peña
It seems like $sFormat can also be of type null; however, parameter $string of strtoupper() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

597
        return in_array(strtoupper(/** @scrutinizer ignore-type */ $sFormat), array_keys(static::$aOutputValidFormats));
Loading history...
598
    }
599
600
    // --------------------------------------------------------------------------
601
602
    /**
603
     * Write a line to the API log
604
     *
605
     * @param string $sLine The line to write
606
     */
607
    public function writeLog($sLine)
608
    {
609
        if (!is_string($sLine)) {
0 ignored issues
show
introduced by Pablo de la Peña
The condition is_string($sLine) is always true.
Loading history...
610
            $sLine = print_r($sLine, true);
611
        }
612
        $sLine = ' [' . $this->sModuleName . '->' . $this->sMethod . '] ' . $sLine;
613
        $this->oLogger->line($sLine);
614
    }
615
616
    // --------------------------------------------------------------------------
617
618
    /**
619
     * Returns the current request method
620
     *
621
     * @return string
622
     */
623
    public function getRequestMethod(): string
624
    {
625
        return $this->sRequestMethod;
626
    }
627
628
    // --------------------------------------------------------------------------
629
630
    /**
631
     * Confirms whether the request is of the supplied type
632
     *
633
     * @param string $sMethod The request method to check
634
     *
635
     * @return bool
636
     */
637
    protected function isRequestMethod(string $sMethod): bool
638
    {
639
        return $this->getRequestMethod() === $sMethod;
640
    }
641
642
    // --------------------------------------------------------------------------
643
644
    /**
645
     * Determines whether the request is a GET request
646
     *
647
     * @return bool
648
     */
649
    public function isGetRequest(): bool
650
    {
651
        return $this->isRequestMethod(static::REQUEST_METHOD_GET);
652
    }
653
654
    // --------------------------------------------------------------------------
655
656
    /**
657
     * Determines whether the request is a PUT request
658
     *
659
     * @return bool
660
     */
661
    public function isPutRequest(): bool
662
    {
663
        return $this->isRequestMethod(static::REQUEST_METHOD_PUT);
664
    }
665
666
    // --------------------------------------------------------------------------
667
668
    /**
669
     * Determines whether the request is a POST request
670
     *
671
     * @return bool
672
     */
673
    public function isPostRequest(): bool
674
    {
675
        return $this->isRequestMethod(static::REQUEST_METHOD_POST);
676
    }
677
678
    // --------------------------------------------------------------------------
679
680
    /**
681
     * Determines whether the request is a DELETE request
682
     *
683
     * @return bool
684
     */
685
    public function isDeleteRequest(): bool
686
    {
687
        return $this->isRequestMethod(static::REQUEST_METHOD_DELETE);
688
    }
689
690
    // --------------------------------------------------------------------------
691
692
    /**
693
     * Returns the current Access Token
694
     *
695
     * @return string|null
696
     */
697
    public function getAccessToken(): ?string
698
    {
699
        return $this->sAccessToken;
700
    }
701
}
702