GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( f28312...62896a )
by
unknown
02:33
created

Output::addHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the O2System Framework package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author         Steeve Andrian Salim
9
 * @copyright      Copyright (c) Steeve Andrian Salim
10
 */
11
12
// ------------------------------------------------------------------------
13
14
namespace O2System\Kernel\Http;
15
16
// ------------------------------------------------------------------------
17
18
use O2System\Gear\Trace;
19
use O2System\Spl\Exceptions\Abstracts\AbstractException;
20
use O2System\Spl\Exceptions\ErrorException;
21
use O2System\Spl\Traits\Collectors\FilePathCollectorTrait;
22
23
/**
24
 * Class Output
25
 *
26
 * @package O2System\Kernel\Http
27
 */
28
class Output extends Message\Response
29
{
30
    use FilePathCollectorTrait;
31
32
    /**
33
     * Output::$mimeType
34
     *
35
     * @var string
36
     */
37
    protected $mimeType = 'text/html';
38
39
    /**
40
     * Output::$charset
41
     *
42
     * @var string
43
     */
44
    protected $charset = 'utf8';
45
46
    // ------------------------------------------------------------------------
47
48
    /**
49
     * Output::__construct
50
     */
51
    public function __construct()
52
    {
53
        parent::__construct();
54
55
        // Set Browser Views Directory
56
        $this->setFileDirName('Views');
57
        $this->addFilePath(PATH_KERNEL);
58
59
        // Autoload exception and error language file
60
        language()->loadFile(['exception', 'error']);
61
62
        // Register Kernel defined handler
63
        $this->register();
64
    }
65
66
    // ------------------------------------------------------------------------
67
68
    /**
69
     * Output::register
70
     *
71
     * Register Kernel defined error, exception and shutdown handler.
72
     *
73
     * @return void
74
     */
75
    final private function register()
76
    {
77
        set_error_handler([&$this, 'errorHandler']);
78
        set_exception_handler([&$this, 'exceptionHandler']);
79
        register_shutdown_function([&$this, 'shutdownHandler']);
80
    }
81
82
    // ------------------------------------------------------------------------
83
84
    /**
85
     * Output::shutdownHandler
86
     *
87
     * Kernel defined shutdown handler function.
88
     *
89
     * @return void
90
     * @throws \O2System\Spl\Exceptions\ErrorException
91
     */
92
    public function shutdownHandler()
93
    {
94
        $lastError = error_get_last();
95
96
        if (is_array($lastError)) {
0 ignored issues
show
introduced by
The condition is_array($lastError) is always true.
Loading history...
97
            $this->errorHandler(
98
                $lastError[ 'type' ],
99
                $lastError[ 'message' ],
100
                $lastError[ 'file' ],
101
                $lastError[ 'line' ]
102
            );
103
        }
104
    }
105
    // --------------------------------------------------------------------
106
107
    /**
108
     * Output::errorHandler
109
     *
110
     * Kernel defined error handler function.
111
     *
112
     * @param int    $errorSeverity The first parameter, errno, contains the level of the error raised, as an integer.
113
     * @param string $errorMessage  The second parameter, errstr, contains the error message, as a string.
114
     * @param string $errorFile     The third parameter is optional, errfile, which contains the filename that the error
115
     *                              was raised in, as a string.
116
     * @param string $errorLine     The fourth parameter is optional, errline, which contains the line number the error
117
     *                              was raised at, as an integer.
118
     * @param array  $errorContext  The fifth parameter is optional, errcontext, which is an array that points to the
119
     *                              active symbol table at the point the error occurred. In other words, errcontext will
120
     *                              contain an array of every variable that existed in the scope the error was triggered
121
     *                              in. User error handler must not modify error context.
122
     *
123
     * @return bool If the function returns FALSE then the normal error handler continues.
124
     * @throws ErrorException
125
     */
126
    public function errorHandler($errorSeverity, $errorMessage, $errorFile, $errorLine, $errorContext = [])
127
    {
128
        $isFatalError = (((E_ERROR | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $errorSeverity) === $errorSeverity);
129
130
        // When the error is fatal the Kernel will throw it as an exception.
131
        if ($isFatalError) {
132
            throw new ErrorException($errorMessage, $errorSeverity, $errorLine, $errorLine, $errorContext);
0 ignored issues
show
Bug introduced by
$errorLine of type string is incompatible with the type integer expected by parameter $line of O2System\Spl\Exceptions\...xception::__construct(). ( Ignorable by Annotation )

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

132
            throw new ErrorException($errorMessage, $errorSeverity, $errorLine, /** @scrutinizer ignore-type */ $errorLine, $errorContext);
Loading history...
133
        }
134
135
        // Should we ignore the error? We'll get the current error_reporting
136
        // level and add its bits with the severity bits to find out.
137
        if (($errorSeverity & error_reporting()) !== $errorSeverity) {
138
            return false;
139
        }
140
141
        $error = new ErrorException($errorMessage, $errorSeverity, $errorFile, $errorLine, $errorContext);
142
143
        // Logged the error
144
        if(services()->has('logger')) {
145
            logger()->error(
146
                implode(
147
                    ' ',
148
                    [
149
                        '[ ' . $error->getStringSeverity() . ' ] ',
150
                        $error->getMessage(),
151
                        $error->getFile() . ':' . $error->getLine(),
152
                    ]
153
                )
154
            );
155
        }
156
        
157
        // Should we display the error?
158
        if (str_ireplace(['off', 'none', 'no', 'false', 'null'], 0, ini_get('display_errors')) == 1) {
159
            if (is_ajax()) {
160
                $this->setContentType('application/json');
161
                $this->statusCode = 500;
162
                $this->reasonPhrase = 'Internal Server Error';
163
164
                $this->send(implode(
165
                    ' ',
166
                    [
167
                        '[ ' . $error->getStringSeverity() . ' ] ',
168
                        $error->getMessage(),
169
                        $error->getFile() . ':' . $error->getLine(),
170
                    ]
171
                ));
172
                exit(EXIT_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
173
            }
174
175
            $filePath = $this->getFilePath('error');
176
177
            ob_start();
178
            include $filePath;
179
            $htmlOutput = ob_get_contents();
180
            ob_end_clean();
181
182
            echo $htmlOutput;
183
            exit(EXIT_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
184
        }
185
    }
186
187
    // ------------------------------------------------------------------------
188
189
    /**
190
     * Output::getFilePath
191
     *
192
     * @param string $filename
193
     *
194
     * @return string
195
     */
196
    public function getFilePath($filename)
197
    {
198
        $filePaths = array_reverse($this->filePaths);
199
200
        foreach ($filePaths as $filePath) {
201
            if (is_file($filePath . $filename . '.phtml')) {
202
                return $filePath . $filename . '.phtml';
203
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
204
            } elseif (is_file($filePath . 'errors' . DIRECTORY_SEPARATOR . $filename . '.phtml')) {
205
                return $filePath . 'errors' . DIRECTORY_SEPARATOR . $filename . '.phtml';
206
                break;
207
            }
208
        }
209
    }
210
211
    // ------------------------------------------------------------------------
212
213
    /**
214
     * Output::setContentType
215
     *
216
     * @param string $mimeType
217
     * @param string $charset
218
     *
219
     * @return $this
220
     */
221
    public function setContentType($mimeType, $charset = null)
222
    {
223
        static $mimes = [];
224
225
        if (empty($mimes)) {
226
            $mimes = require(str_replace('Http', 'Config', __DIR__) . DIRECTORY_SEPARATOR . 'Mimes.php');
227
        }
228
229
        if (strpos($mimeType, '/') === false) {
230
            $extension = ltrim($mimeType, '.');
231
            // Is this extension supported?
232
            if (isset($mimes[ $extension ])) {
233
                $mimeType =& $mimes[ $extension ];
234
                if (is_array($mimeType)) {
235
                    $mimeType = current($mimeType);
236
                }
237
            }
238
        }
239
240
        $this->mimeType = $mimeType;
241
242
        $this->addHeader(
243
            'Content-Type',
244
            $mimeType
245
            . (empty($charset) ? '' : '; charset=' . $charset)
246
        );
247
248
        return $this;
249
    }
250
251
    // ------------------------------------------------------------------------
252
253
    /**
254
     * Output::addHeader
255
     *
256
     * @param string $name
257
     * @param string $value
258
     *
259
     * @return static
260
     */
261
    public function addHeader($name, $value)
262
    {
263
        $this->headers[ $name ] = $value;
264
265
        return $this;
266
    }
267
268
    // ------------------------------------------------------------------------
269
270
    /**
271
     * Output::send
272
     *
273
     * @param       $data
274
     * @param array $headers
275
     */
276
    public function send($data = null, array $headers = [])
277
    {
278
        $statusCode = $this->statusCode;
279
        $reasonPhrase = $this->reasonPhrase;
280
281
        if (is_ajax()) {
282
            $contentType = isset($_SERVER[ 'HTTP_X_REQUESTED_CONTENT_TYPE' ]) ? $_SERVER[ 'HTTP_X_REQUESTED_CONTENT_TYPE' ] : 'application/json';
283
            $this->setContentType($contentType);
284
        }
285
286
        $this->sendHeaders($headers);
287
288
        $response = [
289
            'status'  => (int)$statusCode,
290
            'reason'  => $reasonPhrase,
291
            'success' => $statusCode >= 200 && $statusCode < 300 ? true : false,
292
            'message' => isset($data[ 'message' ]) ? $data[ 'message' ] : '',
293
            'result'  => [],
294
        ];
295
296
        if ($data instanceof \ArrayIterator) {
297
            $data = $data->getArrayCopy();
298
        }
299
300
        if (is_array($data) and count($data)) {
301
            if (is_numeric(key($data))) {
302
                $response[ 'result' ] = $data;
303
            } elseif (is_string(key($data))) {
304
                if (array_key_exists('success', $data)) {
305
                    $response[ 'success' ] = $data[ 'success' ];
306
                    unset($data[ 'success' ]);
307
                }
308
309
                if (array_key_exists('message', $data)) {
310
                    $response[ 'message' ] = $data[ 'message' ];
311
                    unset($data[ 'message' ]);
312
                }
313
314
                if (array_key_exists('timestamp', $data)) {
315
                    $response[ 'timestamp' ] = $data[ 'timestamp' ];
316
                    unset($data[ 'timestamp' ]);
317
                }
318
319
                if (array_key_exists('metadata', $data)) {
320
                    $response[ 'metadata' ] = $data[ 'metadata' ];
321
                    unset($data[ 'metadata' ]);
322
                }
323
324
                if (array_key_exists('errors', $data)) {
325
                    $response[ 'errors' ] = $data[ 'errors' ];
326
                }
327
328
                if (array_key_exists('error', $data)) {
329
                    $response[ 'error' ] = $data[ 'error' ];
330
                }
331
332
                if (array_key_exists('data', $data)) {
333
                    if ($data[ 'data' ] instanceof \ArrayIterator) {
334
                        $data[ 'data' ] = $data[ 'data' ]->getArrayCopy();
335
                    }
336
337
                    if (is_array($data[ 'data' ])) {
338
                        if (is_string(key($data[ 'data' ]))) {
339
                            $response[ 'result' ] = [$data[ 'data' ]];
340
                        } elseif (is_numeric(key($data[ 'data' ]))) {
341
                            $response[ 'result' ] = $data[ 'data' ];
342
                        }
343
                    } else {
344
                        $response[ 'result' ] = [$data[ 'data' ]];
345
                    }
346
                }
347
            }
348
        }
349
350
        if (is_object($data)) {
351
            if (isset($data->success)) {
352
                $response[ 'success' ] = $data->success;
353
                unset($data->success);
354
            }
355
356
            if (isset($data->message)) {
357
                $response[ 'message' ] = $data->message;
358
                unset($data->message);
359
            }
360
361
            if (isset($data->timestamp)) {
362
                $response[ 'timestamp' ] = $data->timestamp;
363
                unset($data->timestamp);
364
            }
365
366
            if (isset($data->metadata)) {
367
                $response[ 'metadata' ] = $data->metadata;
368
                unset($data->metadata);
369
            }
370
371
            if (isset($data->errors)) {
372
                $response[ 'errors' ] = $data->errors;
373
                unset($data->errors);
374
            }
375
376
            if (isset($data->error)) {
377
                $response[ 'error' ] = $data->error;
378
                unset($data->error);
379
            }
380
381
            if (isset($data->data)) {
382
                if ($data->data instanceof \ArrayIterator) {
383
                    $data->data = $data->data->getArrayCopy();
384
                }
385
386
                if (is_array($data->data)) {
387
                    if (is_string(key($data->data))) {
388
                        $response[ 'result' ] = [$data->data];
389
                    } elseif (is_numeric(key($data->data))) {
390
                        $response[ 'result' ] = $data->data;
391
                    }
392
                } else {
393
                    $response[ 'result' ] = [$data->data];
394
                }
395
            }
396
        }
397
398
        if ($this->mimeType === 'application/json') {
399
            if ( ! empty($data)) {
400
                array_push($response[ 'result' ], $data);
401
            }
402
403
            echo json_encode($response, JSON_PRETTY_PRINT);
404
        } elseif ($this->mimeType === 'application/xml') {
405
            $xml = new \SimpleXMLElement('<?xml version="1.0"?><response></response>');
406
            $xml->addAttribute('status', $statusCode);
407
            $xml->addAttribute('reason', $reasonPhrase);
408
409
            if ( ! empty($data)) {
410
                $this->arrayToXml(['message' => $data], $xml);
411
            }
412
            echo $xml->asXML();
413
        } else {
414
            echo $data;
415
        }
416
    }
417
418
    protected function sendHeaders(array $headers = [])
419
    {
420
        ini_set('expose_php', 0);
421
422
        // collect headers that already sent
423
        foreach (headers_list() as $header) {
424
            $headerParts = explode(':', $header);
425
            $headerParts = array_map('trim', $headerParts);
426
            $headers[ $headerParts[ 0 ] ] = $headerParts[ 1 ];
427
            header_remove($header[ 0 ]);
428
        }
429
430
        if (count($headers)) {
431
            $this->headers = array_merge($this->headers, $headers);
432
        }
433
434
        if ($this->statusCode === 204) {
435
            $this->statusCode = 200;
436
            $this->reasonPhrase = 'OK';
437
        }
438
439
        $this->sendHeaderStatus($this->statusCode, $this->reasonPhrase, $this->protocol);
440
441
        foreach ($this->headers as $name => $value) {
442
            $this->sendHeader($name, $value);
443
        }
444
    }
445
446
    // ------------------------------------------------------------------------
447
448
    /**
449
     * Output::sendHeaderStatus
450
     *
451
     * @param int    $statusCode
452
     * @param string $reasonPhrase
453
     * @param string $protocol
454
     *
455
     * @return $this
456
     */
457
    public function sendHeaderStatus($statusCode, $reasonPhrase, $protocol = '1.1')
458
    {
459
        @header('HTTP/' . $protocol . ' ' . $statusCode . ' ' . $reasonPhrase, true);
0 ignored issues
show
Bug introduced by
Are you sure the usage of header('HTTP/' . $protoc... . $reasonPhrase, true) is correct as it 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...
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

459
        /** @scrutinizer ignore-unhandled */ @header('HTTP/' . $protocol . ' ' . $statusCode . ' ' . $reasonPhrase, true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
460
461
        return $this;
462
    }
463
464
    // ------------------------------------------------------------------------
465
466
    /**
467
     * Output::sendHeader
468
     *
469
     * @param string    $name
470
     * @param string    $value
471
     * @param bool      $replace
472
     *
473
     * @return static
474
     */
475
    public function sendHeader($name, $value, $replace = true)
476
    {
477
        @header($name . ': ' . trim($value), $replace);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

477
        /** @scrutinizer ignore-unhandled */ @header($name . ': ' . trim($value), $replace);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Bug introduced by
Are you sure the usage of header($name . ': ' . trim($value), $replace) is correct as it 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...
478
479
        return $this;
480
    }
481
482
    // ------------------------------------------------------------------------
483
484
    /**
485
     * Output::arrayToXml
486
     *
487
     * @param array             $data
488
     * @param \SimpleXMLElement $xml
489
     */
490
    protected function arrayToXml(array $data, \SimpleXMLElement &$xml)
491
    {
492
        foreach ($data as $key => $value) {
493
            if (is_numeric($key)) {
494
                $key = 'item' . $key; //dealing with <0/>..<n/> issues
495
            }
496
            if (is_array($value)) {
497
                $subnode = $xml->addChild($key);
498
                $this->arrayToXml($value, $subnode);
499
            } else {
500
                $xml->addChild("$key", htmlspecialchars("$value"));
501
            }
502
        }
503
    }
504
505
    // ------------------------------------------------------------------------
506
507
    /**
508
     * Output::sendPayload
509
     *
510
     * @param array         $data
511
     * @param string|null   $mimeType
512
     */
513
    public function sendPayload(array $data, $mimeType = null)
514
    {
515
        $mimeType = isset($mimeType) ? $mimeType : $this->mimeType;
516
        $this->setContentType($mimeType);
517
518
        if ($mimeType === 'application/json') {
519
            $payload = json_encode($data, JSON_PRETTY_PRINT);
520
        } elseif ($mimeType === 'application/xml') {
521
            $xml = new \SimpleXMLElement('<?xml version="1.0"?><payload></payload>');
522
            $this->arrayToXml($data, $xml);
523
            $payload = $xml->asXML();
524
        }
525
526
        $this->sendHeaders();
527
        echo $payload;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $payload does not seem to be defined for all execution paths leading up to this point.
Loading history...
528
    }
529
530
    // ------------------------------------------------------------------------
531
532
    /**
533
     * Output::exceptionHandler
534
     *
535
     * Kernel defined exception handler function.
536
     *
537
     * @param \Exception|\Error|\O2System\Spl\Exceptions\Abstracts\AbstractException $exception Throwable exception.
538
     *
539
     * @return void
540
     */
541
    public function exceptionHandler($exception)
542
    {
543
        if (is_ajax()) {
544
            $this->statusCode = 500;
545
            $this->reasonPhrase = 'Internal Server Error';
546
547
            $this->send(implode(
548
                ' ',
549
                [
550
                    ($exception->getCode() != 0 ? '[ ' . $exception->getCode() . ']' : ''),
551
                    $exception->getMessage(),
552
                    $exception->getFile() . ':' . $exception->getLine(),
553
                ]
554
            ));
555
        } elseif ($exception instanceof AbstractException) {
556
557
            ob_start();
558
            include $this->getFilePath('exception');
559
            $htmlOutput = ob_get_contents();
560
            ob_end_clean();
561
562
            echo $htmlOutput;
563
            exit(EXIT_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
564
        } elseif ($exception instanceof \Exception || $exception instanceof \Error) {
0 ignored issues
show
introduced by
$exception is always a sub-type of Error.
Loading history...
565
566
            $exceptionClassName = get_class_name($exception);
567
            $header = language()->getLine('E_HEADER_' . $exceptionClassName);
568
            $description = language()->getLine('E_DESCRIPTION_' . $exceptionClassName);
569
            $trace = new Trace($exception->getTrace());
570
571
            ob_start();
572
            include $this->getFilePath('exception-spl');
573
            $htmlOutput = ob_get_contents();
574
            ob_end_clean();
575
576
            echo $htmlOutput;
577
            exit(EXIT_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
578
        }
579
    }
580
581
    // ------------------------------------------------------------------------
582
583
    /**
584
     * Output::sendError
585
     *
586
     * @param int               $code
587
     * @param null|array|string $vars
588
     * @param array             $headers
589
     */
590
    public function sendError($code = 204, $vars = null, $headers = [])
591
    {
592
        $languageKey = $code . '_' . error_code_string($code);
593
594
        $error = [
595
            'code'    => $code,
596
            'title'   => language()->getLine($languageKey . '_TITLE'),
597
            'message' => language()->getLine($languageKey . '_MESSAGE'),
598
        ];
599
600
        $this->statusCode = $code;
601
        $this->reasonPhrase = $error[ 'title' ];
602
603
        if (is_string($vars)) {
604
            $vars = ['message' => $vars];
605
        } elseif (is_array($vars) and empty($vars[ 'message' ])) {
606
            $vars[ 'message' ] = $error[ 'message' ];
607
        }
608
609
        if (isset($vars[ 'message' ])) {
610
            $error[ 'message' ] = $vars[ 'message' ];
611
        }
612
613
        if (is_ajax() or $this->mimeType !== 'text/html') {
614
            $this->statusCode = $code;
615
            $this->reasonPhrase = $error[ 'title' ];
616
            $this->send($vars);
617
618
            exit(EXIT_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
619
        }
620
621
        $this->sendHeaders($headers);
622
623
        extract($error);
624
625
        ob_start();
626
        include $this->getFilePath('error-code');
627
        $htmlOutput = ob_get_contents();
628
        ob_end_clean();
629
630
        echo $htmlOutput;
631
        exit(EXIT_ERROR);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
632
    }
633
}
634