MOON
Server: Apache
System: Linux server1.studioinfinity.com.br 2.6.32-954.3.5.lve1.4.90.el6.x86_64 #1 SMP Tue Feb 21 12:26:30 UTC 2023 x86_64
User: artinside (517)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system
Upload Files
File: /home/artinside/sites.artinside.com.br/festival/vendor/aplus/debug/src/ExceptionHandler.php
<?php declare(strict_types=1);
/*
 * This file is part of Aplus Framework Debug Library.
 *
 * (c) Natan Felles <natanfelles@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Framework\Debug;

use ErrorException;
use Framework\CLI\CLI;
use Framework\Helpers\Isolation;
use Framework\Language\Language;
use Framework\Log\Logger;
use InvalidArgumentException;
use RuntimeException;
use Throwable;

/**
 * Class ExceptionHandler.
 *
 * @package debug
 */
class ExceptionHandler
{
    /**
     * Development environment.
     */
    public const string DEVELOPMENT = 'development';
    /**
     * Production environment.
     */
    public const string PRODUCTION = 'production';
    protected string $developmentView = __DIR__ . '/Views/exceptions/development.php';
    protected string $productionView = __DIR__ . '/Views/exceptions/production.php';
    protected ?Logger $logger = null;
    protected string $environment = ExceptionHandler::PRODUCTION;
    protected Language $language;
    protected bool $testing = false;
    protected SearchEngines $searchEngines;

    /**
     * ExceptionHandler constructor.
     *
     * @param string $environment
     * @param Logger|null $logger
     * @param Language|null $language
     *
     * @throws InvalidArgumentException if environment is invalid
     */
    public function __construct(
        string $environment = ExceptionHandler::PRODUCTION,
        ?Logger $logger = null,
        ?Language $language = null
    ) {
        $this->setEnvironment($environment);
        if ($logger) {
            $this->logger = $logger;
        }
        if ($language) {
            $this->setLanguage($language);
        }
    }

    public function setEnvironment(string $environment) : static
    {
        if (!\in_array($environment, [
            static::DEVELOPMENT,
            static::PRODUCTION,
        ], true)) {
            throw new InvalidArgumentException('Invalid environment: ' . $environment);
        }
        $this->environment = $environment;
        return $this;
    }

    public function getEnvironment() : string
    {
        return $this->environment;
    }

    /**
     * @return Logger|null
     */
    public function getLogger() : ?Logger
    {
        return $this->logger;
    }

    public function setLanguage(?Language $language = null) : static
    {
        $this->language = $language ?? new Language();
        $this->language->addDirectory(__DIR__ . '/Languages');
        return $this;
    }

    /**
     * @return Language
     */
    public function getLanguage() : Language
    {
        if (!isset($this->language)) {
            $this->setLanguage();
        }
        return $this->language;
    }

    protected function validateView(string $file) : string
    {
        $realpath = \realpath($file);
        if (!$realpath || !\is_file($realpath)) {
            throw new InvalidArgumentException(
                'Invalid exceptions view file: ' . $file
            );
        }
        return $realpath;
    }

    public function setDevelopmentView(string $file) : static
    {
        $this->developmentView = $this->validateView($file);
        return $this;
    }

    public function getDevelopmentView() : string
    {
        return $this->developmentView;
    }

    public function setProductionView(string $file) : static
    {
        $this->productionView = $this->validateView($file);
        return $this;
    }

    public function getProductionView() : string
    {
        return $this->productionView;
    }

    public function initialize(bool $handleErrors = true) : void
    {
        \set_exception_handler([$this, 'exceptionHandler']);
        if ($handleErrors) {
            \set_error_handler([$this, 'errorHandler']);
        }
    }

    /**
     * Exception handler.
     *
     * @param Throwable $exception The Throwable, exception, instance
     *
     * @throws RuntimeException if view file is not found
     */
    public function exceptionHandler(Throwable $exception) : void
    {
        if (\ob_get_length()) {
            \ob_end_clean();
        }
        $this->log((string) $exception);
        if ($this->isCli()) {
            $this->cliError($exception);
            return;
        }
        \http_response_code(500);
        if (!\headers_sent()) {
            $this->sendHeaders();
        }
        if ($this->isJson() || $this->acceptJson()) {
            $this->sendJson($exception);
            return;
        }
        $file = $this->getEnvironment() === static::DEVELOPMENT
            ? $this->getDevelopmentView()
            : $this->getProductionView();
        Isolation::require($file, [
            'handler' => $this,
            'exception' => $exception,
        ]);
    }

    protected function isCli() : bool
    {
        return \PHP_SAPI === 'cli' || \defined('STDIN');
    }

    protected function isJson() : bool
    {
        return isset($_SERVER['HTTP_CONTENT_TYPE'])
            && \str_starts_with($_SERVER['HTTP_CONTENT_TYPE'], 'application/json');
    }

    protected function acceptJson() : bool
    {
        return isset($_SERVER['HTTP_ACCEPT'])
            && \str_contains($_SERVER['HTTP_ACCEPT'], 'application/json');
    }

    protected function sendJson(Throwable $exception) : void
    {
        $data = $this->getEnvironment() === static::DEVELOPMENT
            ? [
                'exception' => $exception::class,
                'message' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => $exception->getTrace(),
            ]
            : [
                'message' => $this->getLanguage()->render('debug', 'exceptionDescription'),
            ];
        echo \json_encode([
            'status' => [
                'code' => 500,
                'reason' => 'Internal Server Error',
            ],
            'data' => $data,
        ], \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
    }

    protected function sendHeaders() : void
    {
        $contentType = 'text/html';
        if ($this->isJson() || $this->acceptJson()) {
            $contentType = 'application/json';
        }
        \header('Content-Type: ' . $contentType . '; charset=UTF-8');
    }

    protected function cliError(Throwable $exception) : void
    {
        $language = $this->getLanguage();
        $message = $language->render('debug', 'exception')
            . ': ' . $exception::class . \PHP_EOL;
        $message .= $language->render('debug', 'message')
            . ': ' . $exception->getMessage() . \PHP_EOL;
        $message .= $language->render('debug', 'file')
            . ': ' . $exception->getFile() . \PHP_EOL;
        $message .= $language->render('debug', 'line')
            . ': ' . $exception->getLine() . \PHP_EOL;
        $message .= $language->render('debug', 'trace')
            . ': ' . $exception->getTraceAsString();
        CLI::error($message, $this->testing ? null : 1);
    }

    protected function log(string $message) : void
    {
        $this->getLogger()?->logCritical($message);
    }

    /**
     * Error handler.
     *
     * @param int $errno The level of the error raised
     * @param string $errstr The error message
     * @param string|null $errfile The filename that the error was raised in
     * @param int|null $errline The line number where the error was raised
     *
     * @see http://php.net/manual/en/function.set-error-handler.php
     *
     * @throws ErrorException if the error is included in the error_reporting
     *
     * @return bool
     */
    public function errorHandler(
        int $errno,
        string $errstr,
        ?string $errfile = null,
        ?int $errline = null
    ) : bool {
        if (!(\error_reporting() & $errno)) {
            return true;
        }
        $type = match ($errno) {
            \E_ERROR => 'Error',
            \E_WARNING => 'Warning',
            \E_PARSE => 'Parse',
            \E_NOTICE => 'Notice',
            \E_CORE_ERROR => 'Core Error',
            \E_CORE_WARNING => 'Core Warning',
            \E_COMPILE_ERROR => 'Compile Error',
            \E_COMPILE_WARNING => 'Compile Warning',
            \E_USER_ERROR => 'User Error',
            \E_USER_WARNING => 'User Warning',
            \E_USER_NOTICE => 'User Notice',
            \E_RECOVERABLE_ERROR => 'Recoverable Error',
            \E_DEPRECATED => 'Deprecated',
            \E_USER_DEPRECATED => 'User Deprecated',
            \E_ALL => 'All',
            default => '',
        };
        throw new ErrorException(
            ($type ? $type . ': ' : '') . $errstr,
            0,
            $errno,
            $errfile,
            $errline
        );
    }

    public function getSearchEngines() : SearchEngines
    {
        if (!isset($this->searchEngines)) {
            $this->setSearchEngines(new SearchEngines());
        }
        return $this->searchEngines;
    }

    public function setSearchEngines(SearchEngines $searchEngines) : static
    {
        $this->searchEngines = $searchEngines;
        return $this;
    }
}