package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,44 +0,0 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->notPath('bootstrap/*')
->notPath('storage/*')
->notPath('resources/view/mail/*')
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->notName('*.blade.php')
->notName('GitConflictController.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
'class_attributes_separation' => [
'elements' => [
'method' => 'one',
],
],
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => true,
],
'single_trait_insert_per_statement' => true,
])
->setFinder($finder);

View File

@@ -1,114 +0,0 @@
# Changelog
All notable changes to `flare-client-php` will be documented in this file
## 1.9.1 - 2021-09-13
- let `report` return the created report
## 1.9.0 - 2021-09-13
- add report tracking uuid
## 1.8.1 - 2021-05-31
- improve compatibility with Symfony 5.3
## 1.8.0 - 2021-04-30
- add ability to ignore errors and exceptions (#23)
- fix curl parameters
## 1.7.0 - 2021-04-12
- use new Flare endpoint and allow 1 redirect to it
## 1.6.1 - 2021-04-08
- make `censorRequestBodyFields` chainable
## 1.6.0 - 2021-04-08
- add ability to censor request body fields (#18)
## 1.5.0 - 2021-03-31
- add `determineVersionUsing`
## 1.4.0 - 2021-02-16
- remove custom grouping
## 1.3.7 - 2020-10-21
- allow PHP 8
## 1.3.6 - 2020-09-18
- remove `larapack/dd` (#15)
## 1.3.5 - 2020-08-26
- allow Laravel 8 (#13)
## 1.3.4 - 2020-07-14
- use directory separator constant
## 1.3.3 - 2020-07-14
- fix tests by requiring symfony/mime
- display real exception class for view errors (see https://github.com/facade/ignition/discussions/237)
## 1.3.2 - 2020-03-02
- allow L7
## 1.3.1 - 2019-12-15
- allow var-dumper v5.0
## 1.3.0 - 2019-11-27
- Allow custom grouping types
## 1.2.1 - 2019-11-19
- Let `registerFlareHandlers` return $this
## 1.2.0 - 2019-11-19
- Add `registerFlareHandlers` method to register error and exception handlers in non-Laravel applications
- Fix get requests with query parameters (#4)
## 1.1.2 - 2019-11-08
- Ignore invalid mime type detection issues
## 1.1.1 - 2019-10-07
- Wrap filesize detection in try-catch block
## 1.1.0 - 2019-09-27
- Add ability to log messages
## 1.0.4 - 2019-09-11
- Fixes an issue when sending exceptions inside a queue worker
## 1.0.3 - 2019-09-05
- Ensure valid session data
## 1.0.2 - 2019-09-05
- Fix error when uploading multiple files using an array name
## 1.0.1 - 2019-09-02
- Fix issue with uploaded files in request context
## 1.0.0 - 2019-08-30
- initial release

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) Facade <info@facade.company>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,36 +0,0 @@
# Send PHP errors to Flare
[![Latest Version on Packagist](https://img.shields.io/packagist/v/facade/flare-client-php.svg?style=flat-square)](https://packagist.org/packages/facade/flare-client-php)
![Tests](https://github.com/facade/flare-client-php/workflows/Run%20tests/badge.svg)
[![Total Downloads](https://img.shields.io/packagist/dt/facade/flare-client-php.svg?style=flat-square)](https://packagist.org/packages/facade/flare-client-php)
This repository contains a PHP client to send PHP errors to [Flare](https://flareapp.io).
![Screenshot of error in Flare](https://facade.github.io/flare-client-php/screenshot.png)
## Documentation
You can find the documentation of this package at [the docs of Flare](https://flareapp.io/docs/general/projects).
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
## Testing
``` bash
composer test
```
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
## Security
If you discover any security related issues, please email support@flareapp.io instead of using the issue tracker.
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

View File

@@ -1,52 +0,0 @@
{
"name": "facade/flare-client-php",
"description": "Send PHP errors to Flare",
"keywords": [
"facade",
"flare",
"exception",
"reporting"
],
"homepage": "https://github.com/facade/flare-client-php",
"license": "MIT",
"require": {
"php": "^7.1|^8.0",
"facade/ignition-contracts": "~1.0",
"illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0",
"symfony/http-foundation": "^3.3|^4.1|^5.0",
"symfony/mime": "^3.4|^4.0|^5.1",
"symfony/var-dumper": "^3.4|^4.0|^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"spatie/phpunit-snapshot-assertions": "^2.0",
"phpunit/phpunit": "^7.5"
},
"autoload": {
"psr-4": {
"Facade\\FlareClient\\": "src"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Facade\\FlareClient\\Tests\\": "tests"
}
},
"scripts": {
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes",
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
}
}

View File

@@ -1,77 +0,0 @@
<?php
namespace Facade\FlareClient;
use Exception;
use Facade\FlareClient\Http\Client;
use Facade\FlareClient\Truncation\ReportTrimmer;
class Api
{
/** @var \Facade\FlareClient\Http\Client */
protected $client;
/** @var bool */
public static $sendInBatches = true;
/** @var array */
protected $queue = [];
public function __construct(Client $client)
{
$this->client = $client;
register_shutdown_function([$this, 'sendQueuedReports']);
}
public static function sendReportsInBatches(bool $batchSending = true)
{
static::$sendInBatches = $batchSending;
}
public function report(Report $report)
{
try {
if (static::$sendInBatches) {
$this->addReportToQueue($report);
} else {
$this->sendReportToApi($report);
}
} catch (Exception $e) {
//
}
}
public function sendTestReport(Report $report)
{
$this->sendReportToApi($report);
}
protected function addReportToQueue(Report $report)
{
$this->queue[] = $report;
}
public function sendQueuedReports()
{
try {
foreach ($this->queue as $report) {
$this->sendReportToApi($report);
}
} catch (Exception $e) {
//
} finally {
$this->queue = [];
}
}
protected function sendReportToApi(Report $report)
{
$this->client->post('reports', $this->truncateReport($report->toArray()));
}
protected function truncateReport(array $payload): array
{
return (new ReportTrimmer())->trim($payload);
}
}

View File

@@ -1,51 +0,0 @@
<?php
namespace Facade\FlareClient\Concerns;
trait HasContext
{
/** @var string|null */
private $messageLevel;
/** @var string|null */
private $stage;
/** @var array */
private $userProvidedContext = [];
public function stage(?string $stage)
{
$this->stage = $stage;
return $this;
}
public function messageLevel(?string $messageLevel)
{
$this->messageLevel = $messageLevel;
return $this;
}
public function getGroup(string $groupName = 'context', $default = []): array
{
return $this->userProvidedContext[$groupName] ?? $default;
}
public function context($key, $value)
{
return $this->group('context', [$key => $value]);
}
public function group(string $groupName, array $properties)
{
$group = $this->userProvidedContext[$groupName] ?? [];
$this->userProvidedContext[$groupName] = array_merge_recursive_distinct(
$group,
$properties
);
return $this;
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace Facade\FlareClient\Concerns;
use Facade\FlareClient\Time\SystemTime;
use Facade\FlareClient\Time\Time;
trait UsesTime
{
/** @var \Facade\FlareClient\Time\Time */
public static $time;
public static function useTime(Time $time)
{
self::$time = $time;
}
public function getCurrentTime(): int
{
$time = self::$time ?? new SystemTime();
return $time->getCurrentTime();
}
}

View File

@@ -1,21 +0,0 @@
<?php
namespace Facade\FlareClient\Context;
class ConsoleContext implements ContextInterface
{
/** @var array */
private $arguments = [];
public function __construct(array $arguments = [])
{
$this->arguments = $arguments;
}
public function toArray(): array
{
return [
'arguments' => $this->arguments,
];
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace Facade\FlareClient\Context;
class ContextContextDetector implements ContextDetectorInterface
{
public function detectCurrentContext(): ContextInterface
{
if ($this->runningInConsole()) {
return new ConsoleContext($_SERVER['argv'] ?? []);
}
return new RequestContext();
}
private function runningInConsole(): bool
{
if (isset($_ENV['APP_RUNNING_IN_CONSOLE'])) {
return $_ENV['APP_RUNNING_IN_CONSOLE'] === 'true';
}
if (isset($_ENV['FLARE_FAKE_WEB_REQUEST'])) {
return false;
}
return in_array(php_sapi_name(), ['cli', 'phpdb']);
}
}

View File

@@ -1,8 +0,0 @@
<?php
namespace Facade\FlareClient\Context;
interface ContextDetectorInterface
{
public function detectCurrentContext(): ContextInterface;
}

View File

@@ -1,8 +0,0 @@
<?php
namespace Facade\FlareClient\Context;
interface ContextInterface
{
public function toArray(): array;
}

View File

@@ -1,126 +0,0 @@
<?php
namespace Facade\FlareClient\Context;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Mime\Exception\InvalidArgumentException;
use Throwable;
class RequestContext implements ContextInterface
{
/** @var \Symfony\Component\HttpFoundation\Request|null */
protected $request;
public function __construct(Request $request = null)
{
$this->request = $request ?? Request::createFromGlobals();
}
public function getRequest(): array
{
return [
'url' => $this->request->getUri(),
'ip' => $this->request->getClientIp(),
'method' => $this->request->getMethod(),
'useragent' => $this->request->headers->get('User-Agent'),
];
}
private function getFiles(): array
{
if (is_null($this->request->files)) {
return [];
}
return $this->mapFiles($this->request->files->all());
}
protected function mapFiles(array $files)
{
return array_map(function ($file) {
if (is_array($file)) {
return $this->mapFiles($file);
}
if (! $file instanceof UploadedFile) {
return;
}
try {
$fileSize = $file->getSize();
} catch (\RuntimeException $e) {
$fileSize = 0;
}
try {
$mimeType = $file->getMimeType();
} catch (InvalidArgumentException $e) {
$mimeType = 'undefined';
}
return [
'pathname' => $file->getPathname(),
'size' => $fileSize,
'mimeType' => $mimeType,
];
}, $files);
}
public function getSession(): array
{
try {
$session = $this->request->getSession();
} catch (\Exception $exception) {
$session = [];
}
return $session ? $this->getValidSessionData($session) : [];
}
/**
* @param SessionInterface $session
* @return array
*/
protected function getValidSessionData($session): array
{
try {
json_encode($session->all());
} catch (Throwable $e) {
return [];
}
return $session->all();
}
public function getCookies(): array
{
return $this->request->cookies->all();
}
public function getHeaders(): array
{
return $this->request->headers->all();
}
public function getRequestData(): array
{
return [
'queryString' => $this->request->query->all(),
'body' => $this->request->request->all(),
'files' => $this->getFiles(),
];
}
public function toArray(): array
{
return [
'request' => $this->getRequest(),
'request_data' => $this->getRequestData(),
'headers' => $this->getHeaders(),
'cookies' => $this->getCookies(),
'session' => $this->getSession(),
];
}
}

View File

@@ -1,8 +0,0 @@
<?php
namespace Facade\FlareClient\Contracts;
interface ProvidesFlareContext
{
public function context(): array;
}

View File

@@ -1,11 +0,0 @@
<?php
namespace Facade\FlareClient\Enums;
/** @deprecated */
class GroupingTypes
{
public const TOP_FRAME = 'topFrame';
public const EXCEPTION = 'exceptionClass';
}

View File

@@ -1,16 +0,0 @@
<?php
namespace Facade\FlareClient\Enums;
class MessageLevels
{
public const INFO = 'info';
public const DEBUG = 'debug';
public const WARNING = 'warning';
public const ERROR = 'error';
public const CRITICAL = 'critical';
}

View File

@@ -1,333 +0,0 @@
<?php
namespace Facade\FlareClient;
use Error;
use ErrorException;
use Exception;
use Facade\FlareClient\Concerns\HasContext;
use Facade\FlareClient\Context\ContextContextDetector;
use Facade\FlareClient\Context\ContextDetectorInterface;
use Facade\FlareClient\Enums\MessageLevels;
use Facade\FlareClient\Glows\Glow;
use Facade\FlareClient\Glows\Recorder;
use Facade\FlareClient\Http\Client;
use Facade\FlareClient\Middleware\AddGlows;
use Facade\FlareClient\Middleware\AnonymizeIp;
use Facade\FlareClient\Middleware\CensorRequestBodyFields;
use Illuminate\Contracts\Container\Container;
use Illuminate\Pipeline\Pipeline;
use Throwable;
class Flare
{
use HasContext;
/** @var \Facade\FlareClient\Http\Client */
protected $client;
/** @var \Facade\FlareClient\Api */
protected $api;
/** @var array */
protected $middleware = [];
/** @var \Facade\FlareClient\Glows\Recorder */
protected $recorder;
/** @var string */
protected $applicationPath;
/** @var \Illuminate\Contracts\Container\Container|null */
protected $container;
/** @var ContextDetectorInterface */
protected $contextDetector;
/** @var callable|null */
protected $previousExceptionHandler;
/** @var callable|null */
protected $previousErrorHandler;
/** @var callable|null */
protected $determineVersionCallable;
/** @var int|null */
protected $reportErrorLevels;
/** @var callable|null */
protected $filterExceptionsCallable;
/** @var callable|null */
protected $filterReportsCallable;
public static function register(string $apiKey, string $apiSecret = null, ContextDetectorInterface $contextDetector = null, Container $container = null)
{
$client = new Client($apiKey, $apiSecret);
return new static($client, $contextDetector, $container);
}
public function determineVersionUsing($determineVersionCallable)
{
$this->determineVersionCallable = $determineVersionCallable;
}
public function reportErrorLevels(int $reportErrorLevels)
{
$this->reportErrorLevels = $reportErrorLevels;
}
public function filterExceptionsUsing(callable $filterExceptionsCallable)
{
$this->filterExceptionsCallable = $filterExceptionsCallable;
}
public function filterReportsUsing(callable $filterReportsCallable)
{
$this->filterReportsCallable = $filterReportsCallable;
}
/**
* @return null|string
*/
public function version()
{
if (! $this->determineVersionCallable) {
return null;
}
return ($this->determineVersionCallable)();
}
public function __construct(Client $client, ContextDetectorInterface $contextDetector = null, Container $container = null, array $middleware = [])
{
$this->client = $client;
$this->recorder = new Recorder();
$this->contextDetector = $contextDetector ?? new ContextContextDetector();
$this->container = $container;
$this->middleware = $middleware;
$this->api = new Api($this->client);
$this->registerDefaultMiddleware();
}
public function getMiddleware(): array
{
return $this->middleware;
}
public function registerFlareHandlers()
{
$this->registerExceptionHandler();
$this->registerErrorHandler();
return $this;
}
public function registerExceptionHandler()
{
$this->previousExceptionHandler = set_exception_handler([$this, 'handleException']);
return $this;
}
public function registerErrorHandler()
{
$this->previousErrorHandler = set_error_handler([$this, 'handleError']);
return $this;
}
private function registerDefaultMiddleware()
{
return $this->registerMiddleware(new AddGlows($this->recorder));
}
public function registerMiddleware($callable)
{
$this->middleware[] = $callable;
return $this;
}
public function getMiddlewares(): array
{
return $this->middleware;
}
public function glow(
string $name,
string $messageLevel = MessageLevels::INFO,
array $metaData = []
) {
$this->recorder->record(new Glow($name, $messageLevel, $metaData));
}
public function handleException(Throwable $throwable)
{
$this->report($throwable);
if ($this->previousExceptionHandler) {
call_user_func($this->previousExceptionHandler, $throwable);
}
}
public function handleError($code, $message, $file = '', $line = 0)
{
$exception = new ErrorException($message, 0, $code, $file, $line);
$this->report($exception);
if ($this->previousErrorHandler) {
return call_user_func(
$this->previousErrorHandler,
$message,
$code,
$file,
$line
);
}
}
public function applicationPath(string $applicationPath)
{
$this->applicationPath = $applicationPath;
return $this;
}
public function report(Throwable $throwable, callable $callback = null): ?Report
{
if (! $this->shouldSendReport($throwable)) {
return null;
}
$report = $this->createReport($throwable);
if (! is_null($callback)) {
call_user_func($callback, $report);
}
$this->sendReportToApi($report);
return $report;
}
protected function shouldSendReport(Throwable $throwable): bool
{
if ($this->reportErrorLevels && $throwable instanceof Error) {
return $this->reportErrorLevels & $throwable->getCode();
}
if ($this->reportErrorLevels && $throwable instanceof ErrorException) {
return $this->reportErrorLevels & $throwable->getSeverity();
}
if ($this->filterExceptionsCallable && $throwable instanceof Exception) {
return call_user_func($this->filterExceptionsCallable, $throwable);
}
return true;
}
public function reportMessage(string $message, string $logLevel, callable $callback = null)
{
$report = $this->createReportFromMessage($message, $logLevel);
if (! is_null($callback)) {
call_user_func($callback, $report);
}
$this->sendReportToApi($report);
}
public function sendTestReport(Throwable $throwable)
{
$this->api->sendTestReport($this->createReport($throwable));
}
private function sendReportToApi(Report $report)
{
if ($this->filterReportsCallable) {
if (! call_user_func($this->filterReportsCallable, $report)) {
return;
}
}
try {
$this->api->report($report);
} catch (Exception $exception) {
}
}
public function reset()
{
$this->api->sendQueuedReports();
$this->userProvidedContext = [];
$this->recorder->reset();
}
private function applyAdditionalParameters(Report $report)
{
$report
->stage($this->stage)
->messageLevel($this->messageLevel)
->setApplicationPath($this->applicationPath)
->userProvidedContext($this->userProvidedContext);
}
public function anonymizeIp()
{
$this->registerMiddleware(new AnonymizeIp());
return $this;
}
public function censorRequestBodyFields(array $fieldNames)
{
$this->registerMiddleware(new CensorRequestBodyFields($fieldNames));
return $this;
}
public function createReport(Throwable $throwable): Report
{
$report = Report::createForThrowable(
$throwable,
$this->contextDetector->detectCurrentContext(),
$this->applicationPath,
$this->version()
);
return $this->applyMiddlewareToReport($report);
}
public function createReportFromMessage(string $message, string $logLevel): Report
{
$report = Report::createForMessage(
$message,
$logLevel,
$this->contextDetector->detectCurrentContext(),
$this->applicationPath
);
return $this->applyMiddlewareToReport($report);
}
protected function applyMiddlewareToReport(Report $report): Report
{
$this->applyAdditionalParameters($report);
$report = (new Pipeline($this->container))
->send($report)
->through($this->middleware)
->then(function ($report) {
return $report;
});
return $report;
}
}

View File

@@ -1,66 +0,0 @@
<?php
namespace Facade\FlareClient;
use Facade\FlareClient\Stacktrace\Codesnippet;
class Frame
{
/** @var string */
protected $file;
/** @var int */
protected $lineNumber;
/** @var string */
protected $method;
/** @var string */
protected $class;
public function __construct(
string $file,
int $lineNumber,
string $method = null,
string $class = null
) {
$this->file = $file;
$this->lineNumber = $lineNumber;
$this->method = $method;
$this->class = $class;
}
public function toArray(): array
{
$codeSnippet = (new Codesnippet())
->snippetLineCount(9)
->surroundingLine($this->lineNumber)
->get($this->file);
return [
'line_number' => $this->lineNumber,
'method' => $this->getFullMethod(),
'code_snippet' => $codeSnippet,
'file' => $this->file,
];
}
private function getFullMethod(): string
{
$method = $this->method;
if ($class = $this->class ?? false) {
$method = "{$class}::{$method}";
}
return $method;
}
public function getFile(): string
{
return $this->file;
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace Facade\FlareClient\Glows;
use Facade\FlareClient\Concerns\UsesTime;
use Facade\FlareClient\Enums\MessageLevels;
class Glow
{
use UsesTime;
/** @var string */
private $name;
/** @var array */
private $metaData;
/** @var string */
private $messageLevel;
/** @var float */
private $microtime;
public function __construct(string $name, string $messageLevel = MessageLevels::INFO, array $metaData = [], ?float $microtime = null)
{
$this->name = $name;
$this->messageLevel = $messageLevel;
$this->metaData = $metaData;
$this->microtime = $microtime ?? microtime(true);
}
public function toArray()
{
return [
'time' => $this->getCurrentTime(),
'name' => $this->name,
'message_level' => $this->messageLevel,
'meta_data' => $this->metaData,
'microtime' => $this->microtime,
];
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace Facade\FlareClient\Glows;
class Recorder
{
public const GLOW_LIMIT = 30;
private $glows = [];
public function record(Glow $glow)
{
$this->glows[] = $glow;
$this->glows = array_slice($this->glows, static::GLOW_LIMIT * -1, static::GLOW_LIMIT);
}
public function glows(): array
{
return $this->glows;
}
public function reset()
{
$this->glows = [];
}
}

View File

@@ -1,217 +0,0 @@
<?php
namespace Facade\FlareClient\Http;
use Facade\FlareClient\Http\Exceptions\BadResponseCode;
use Facade\FlareClient\Http\Exceptions\InvalidData;
use Facade\FlareClient\Http\Exceptions\MissingParameter;
use Facade\FlareClient\Http\Exceptions\NotFound;
class Client
{
/** @var null|string */
private $apiToken;
/** @var null|string */
private $apiSecret;
/** @var string */
private $baseUrl;
/** @var int */
private $timeout;
public function __construct(
?string $apiToken,
?string $apiSecret,
string $baseUrl = 'https://reporting.flareapp.io/api',
int $timeout = 10
) {
$this->apiToken = $apiToken;
$this->apiSecret = $apiSecret;
if (! $baseUrl) {
throw MissingParameter::create('baseUrl');
}
$this->baseUrl = $baseUrl;
if (! $timeout) {
throw MissingParameter::create('timeout');
}
$this->timeout = $timeout;
}
/**
* @param string $url
* @param array $arguments
*
* @return array|false
*/
public function get(string $url, array $arguments = [])
{
return $this->makeRequest('get', $url, $arguments);
}
/**
* @param string $url
* @param array $arguments
*
* @return array|false
*/
public function post(string $url, array $arguments = [])
{
return $this->makeRequest('post', $url, $arguments);
}
/**
* @param string $url
* @param array $arguments
*
* @return array|false
*/
public function patch(string $url, array $arguments = [])
{
return $this->makeRequest('patch', $url, $arguments);
}
/**
* @param string $url
* @param array $arguments
*
* @return array|false
*/
public function put(string $url, array $arguments = [])
{
return $this->makeRequest('put', $url, $arguments);
}
/**
* @param string $method
* @param array $arguments
*
* @return array|false
*/
public function delete(string $method, array $arguments = [])
{
return $this->makeRequest('delete', $method, $arguments);
}
/**
* @param string $httpVerb
* @param string $url
* @param array $arguments
*
* @return array
*/
private function makeRequest(string $httpVerb, string $url, array $arguments = [])
{
$queryString = http_build_query([
'key' => $this->apiToken,
'secret' => $this->apiSecret,
]);
$fullUrl = "{$this->baseUrl}/{$url}?{$queryString}";
$headers = [
'x-api-token: '.$this->apiToken,
];
$response = $this->makeCurlRequest($httpVerb, $fullUrl, $headers, $arguments);
if ($response->getHttpResponseCode() === 422) {
throw InvalidData::createForResponse($response);
}
if ($response->getHttpResponseCode() === 404) {
throw NotFound::createForResponse($response);
}
if ($response->getHttpResponseCode() !== 200 && $response->getHttpResponseCode() !== 204) {
throw BadResponseCode::createForResponse($response);
}
return $response->getBody();
}
public function makeCurlRequest(string $httpVerb, string $fullUrl, array $headers = [], array $arguments = []): Response
{
$curlHandle = $this->getCurlHandle($fullUrl, $headers);
switch ($httpVerb) {
case 'post':
curl_setopt($curlHandle, CURLOPT_POST, true);
$this->attachRequestPayload($curlHandle, $arguments);
break;
case 'get':
curl_setopt($curlHandle, CURLOPT_URL, $fullUrl.'&'.http_build_query($arguments));
break;
case 'delete':
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
case 'patch':
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PATCH');
$this->attachRequestPayload($curlHandle, $arguments);
break;
case 'put':
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PUT');
$this->attachRequestPayload($curlHandle, $arguments);
break;
}
$body = json_decode(curl_exec($curlHandle), true);
$headers = curl_getinfo($curlHandle);
$error = curl_error($curlHandle);
return new Response($headers, $body, $error);
}
private function attachRequestPayload(&$curlHandle, array $data)
{
$encoded = json_encode($data);
$this->lastRequest['body'] = $encoded;
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $encoded);
}
/**
* @param string $fullUrl
* @param array $headers
*
* @return resource
*/
private function getCurlHandle(string $fullUrl, array $headers = [])
{
$curlHandle = curl_init();
curl_setopt($curlHandle, CURLOPT_URL, $fullUrl);
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge([
'Accept: application/json',
'Content-Type: application/json',
], $headers));
curl_setopt($curlHandle, CURLOPT_USERAGENT, 'Laravel/Flare API 1.0');
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($curlHandle, CURLOPT_ENCODING, '');
curl_setopt($curlHandle, CURLINFO_HEADER_OUT, true);
curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curlHandle, CURLOPT_MAXREDIRS, 1);
return $curlHandle;
}
}

View File

@@ -1,21 +0,0 @@
<?php
namespace Facade\FlareClient\Http\Exceptions;
use Exception;
use Facade\FlareClient\Http\Response;
class BadResponse extends Exception
{
/** @var \Facade\FlareClient\Http\Response */
public $response;
public static function createForResponse(Response $response)
{
$exception = new static("Could not perform request because: {$response->getError()}");
$exception->response = $response;
return $exception;
}
}

View File

@@ -1,33 +0,0 @@
<?php
namespace Facade\FlareClient\Http\Exceptions;
use Exception;
use Facade\FlareClient\Http\Response;
class BadResponseCode extends Exception
{
/** @var \Facade\FlareClient\Http\Response */
public $response;
/** @var array */
public $errors;
public static function createForResponse(Response $response)
{
$exception = new static(static::getMessageForResponse($response));
$exception->response = $response;
$bodyErrors = isset($response->getBody()['errors']) ? $response->getBody()['errors'] : [];
$exception->errors = $bodyErrors;
return $exception;
}
public static function getMessageForResponse(Response $response)
{
return "Response code {$response->getHttpResponseCode()} returned";
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Facade\FlareClient\Http\Exceptions;
use Facade\FlareClient\Http\Response;
class InvalidData extends BadResponseCode
{
public static function getMessageForResponse(Response $response)
{
return 'Invalid data found';
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Facade\FlareClient\Http\Exceptions;
use Exception;
class MissingParameter extends Exception
{
public static function create(string $parameterName)
{
return new static("`$parameterName` is a required parameter");
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Facade\FlareClient\Http\Exceptions;
use Facade\FlareClient\Http\Response;
class NotFound extends BadResponseCode
{
public static function getMessageForResponse(Response $response)
{
return 'Not found';
}
}

View File

@@ -1,65 +0,0 @@
<?php
namespace Facade\FlareClient\Http;
class Response
{
private $headers;
private $body;
private $error;
public function __construct($headers, $body, $error)
{
$this->headers = $headers;
$this->body = $body;
$this->error = $error;
}
/**
* @return mixed
*/
public function getHeaders()
{
return $this->headers;
}
/**
* @return mixed
*/
public function getBody()
{
return $this->body;
}
/**
* @return bool
*/
public function hasBody()
{
return $this->body != false;
}
/**
* @return mixed
*/
public function getError()
{
return $this->error;
}
/**
* @return null|int
*/
public function getHttpResponseCode()
{
if (! isset($this->headers['http_code'])) {
return;
}
return (int) $this->headers['http_code'];
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace Facade\FlareClient\Middleware;
use Facade\FlareClient\Glows\Recorder;
use Facade\FlareClient\Report;
class AddGlows
{
/** @var Recorder */
private $recorder;
public function __construct(Recorder $recorder)
{
$this->recorder = $recorder;
}
public function handle(Report $report, $next)
{
foreach ($this->recorder->glows() as $glow) {
$report->addGlow($glow);
}
return $next($report);
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace Facade\FlareClient\Middleware;
use Facade\FlareClient\Report;
class AnonymizeIp
{
public function handle(Report $report, $next)
{
$context = $report->allContext();
$context['request']['ip'] = null;
$report->userProvidedContext($context);
return $next($report);
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace Facade\FlareClient\Middleware;
use Facade\FlareClient\Report;
class CensorRequestBodyFields
{
protected $fieldNames = [];
public function __construct(array $fieldNames)
{
$this->fieldNames = $fieldNames;
}
public function handle(Report $report, $next)
{
$context = $report->allContext();
foreach ($this->fieldNames as $fieldName) {
if (isset($context['request_data']['body'][$fieldName])) {
$context['request_data']['body'][$fieldName] = '<CENSORED>';
}
}
$report->userProvidedContext($context);
return $next($report);
}
}

View File

@@ -1,334 +0,0 @@
<?php
namespace Facade\FlareClient;
use Facade\FlareClient\Concerns\HasContext;
use Facade\FlareClient\Concerns\UsesTime;
use Facade\FlareClient\Context\ContextInterface;
use Facade\FlareClient\Contracts\ProvidesFlareContext;
use Facade\FlareClient\Enums\GroupingTypes;
use Facade\FlareClient\Glows\Glow;
use Facade\FlareClient\Solutions\ReportSolution;
use Facade\FlareClient\Stacktrace\Stacktrace;
use Facade\IgnitionContracts\Solution;
use Throwable;
class Report
{
use UsesTime;
use HasContext;
/** @var \Facade\FlareClient\Stacktrace\Stacktrace */
private $stacktrace;
/** @var string */
private $exceptionClass;
/** @var string */
private $message;
/** @var array */
private $glows = [];
/** @var array */
private $solutions = [];
/** @var ContextInterface */
private $context;
/** @var string */
private $applicationPath;
/** @var ?string */
private $applicationVersion;
/** @var array */
private $userProvidedContext = [];
/** @var array */
private $exceptionContext = [];
/** @var Throwable */
private $throwable;
/** @var string */
private $notifierName;
/** @var string */
private $languageVersion;
/** @var string */
private $frameworkVersion;
/** @var int */
private $openFrameIndex;
/** @var string */
private $groupBy ;
/** @var string */
private $trackingUuid;
/** @var null string|null */
public static $fakeTrackingUuid = null;
public static function createForThrowable(
Throwable $throwable,
ContextInterface $context,
?string $applicationPath = null,
?string $version = null
): self {
return (new static())
->setApplicationPath($applicationPath)
->throwable($throwable)
->useContext($context)
->exceptionClass(self::getClassForThrowable($throwable))
->message($throwable->getMessage())
->stackTrace(Stacktrace::createForThrowable($throwable, $applicationPath))
->exceptionContext($throwable)
->setApplicationVersion($version);
}
protected static function getClassForThrowable(Throwable $throwable): string
{
if ($throwable instanceof \Facade\Ignition\Exceptions\ViewException) {
if ($previous = $throwable->getPrevious()) {
return get_class($previous);
}
}
return get_class($throwable);
}
public static function createForMessage(string $message, string $logLevel, ContextInterface $context, ?string $applicationPath = null): self
{
$stacktrace = Stacktrace::create($applicationPath);
return (new static())
->setApplicationPath($applicationPath)
->message($message)
->useContext($context)
->exceptionClass($logLevel)
->stacktrace($stacktrace)
->openFrameIndex($stacktrace->firstApplicationFrameIndex());
}
public function __construct()
{
$this->trackingUuid = self::$fakeTrackingUuid ?? $this->generateUuid();
}
public function trackingUuid(): string
{
return $this->trackingUuid;
}
public function exceptionClass(string $exceptionClass)
{
$this->exceptionClass = $exceptionClass;
return $this;
}
public function getExceptionClass(): string
{
return $this->exceptionClass;
}
public function throwable(Throwable $throwable)
{
$this->throwable = $throwable;
return $this;
}
public function getThrowable(): ?Throwable
{
return $this->throwable;
}
public function message(string $message)
{
$this->message = $message;
return $this;
}
public function getMessage(): string
{
return $this->message;
}
public function stacktrace(Stacktrace $stacktrace)
{
$this->stacktrace = $stacktrace;
return $this;
}
public function getStacktrace(): Stacktrace
{
return $this->stacktrace;
}
public function notifierName(string $notifierName)
{
$this->notifierName = $notifierName;
return $this;
}
public function languageVersion(string $languageVersion)
{
$this->languageVersion = $languageVersion;
return $this;
}
public function frameworkVersion(string $frameworkVersion)
{
$this->frameworkVersion = $frameworkVersion;
return $this;
}
public function useContext(ContextInterface $request)
{
$this->context = $request;
return $this;
}
public function openFrameIndex(?int $index)
{
$this->openFrameIndex = $index;
return $this;
}
public function setApplicationPath(?string $applicationPath)
{
$this->applicationPath = $applicationPath;
return $this;
}
public function getApplicationPath(): ?string
{
return $this->applicationPath;
}
public function setApplicationVersion(?string $applicationVersion)
{
$this->applicationVersion = $applicationVersion;
return $this;
}
public function getApplicationVersion(): ?string
{
return $this->applicationVersion;
}
public function view(?View $view)
{
$this->view = $view;
return $this;
}
public function addGlow(Glow $glow)
{
$this->glows[] = $glow->toArray();
return $this;
}
public function addSolution(Solution $solution)
{
$this->solutions[] = ReportSolution::fromSolution($solution)->toArray();
return $this;
}
public function userProvidedContext(array $userProvidedContext)
{
$this->userProvidedContext = $userProvidedContext;
return $this;
}
/** @deprecated */
public function groupByTopFrame()
{
$this->groupBy = GroupingTypes::TOP_FRAME;
return $this;
}
/** @deprecated */
public function groupByException()
{
$this->groupBy = GroupingTypes::EXCEPTION;
return $this;
}
public function allContext(): array
{
$context = $this->context->toArray();
$context = array_merge_recursive_distinct($context, $this->exceptionContext);
return array_merge_recursive_distinct($context, $this->userProvidedContext);
}
private function exceptionContext(Throwable $throwable)
{
if ($throwable instanceof ProvidesFlareContext) {
$this->exceptionContext = $throwable->context();
}
return $this;
}
public function toArray()
{
return [
'notifier' => $this->notifierName ?? 'Flare Client',
'language' => 'PHP',
'framework_version' => $this->frameworkVersion,
'language_version' => $this->languageVersion ?? phpversion(),
'exception_class' => $this->exceptionClass,
'seen_at' => $this->getCurrentTime(),
'message' => $this->message,
'glows' => $this->glows,
'solutions' => $this->solutions,
'stacktrace' => $this->stacktrace->toArray(),
'context' => $this->allContext(),
'stage' => $this->stage,
'message_level' => $this->messageLevel,
'open_frame_index' => $this->openFrameIndex,
'application_path' => $this->applicationPath,
'application_version' => $this->applicationVersion,
'tracking_uuid' => $this->trackingUuid,
];
}
/*
* Found on https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
private function generateUuid(): string
{
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
$data = $data ?? random_bytes(16);
// Set version to 0100
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
// Set bits 6-7 to 10
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
}

View File

@@ -1,36 +0,0 @@
<?php
namespace Facade\FlareClient\Solutions;
use Facade\IgnitionContracts\RunnableSolution;
use Facade\IgnitionContracts\Solution as SolutionContract;
class ReportSolution
{
/** @var SolutionContract */
protected $solution;
public function __construct(SolutionContract $solution)
{
$this->solution = $solution;
}
public static function fromSolution(SolutionContract $solution)
{
return new static($solution);
}
public function toArray(): array
{
$isRunnable = ($this->solution instanceof RunnableSolution);
return [
'class' => get_class($this->solution),
'title' => $this->solution->getSolutionTitle(),
'description' => $this->solution->getSolutionDescription(),
'links' => $this->solution->getDocumentationLinks(),
'action_description' => $isRunnable ? $this->solution->getSolutionActionDescription() : null,
'is_runnable' => $isRunnable,
];
}
}

View File

@@ -1,72 +0,0 @@
<?php
namespace Facade\FlareClient\Stacktrace;
use RuntimeException;
class Codesnippet
{
/** @var int */
private $surroundingLine = 1;
/** @var int */
private $snippetLineCount = 9;
public function surroundingLine(int $surroundingLine): self
{
$this->surroundingLine = $surroundingLine;
return $this;
}
public function snippetLineCount(int $snippetLineCount): self
{
$this->snippetLineCount = $snippetLineCount;
return $this;
}
public function get(string $fileName): array
{
if (! file_exists($fileName)) {
return [];
}
try {
$file = new File($fileName);
[$startLineNumber, $endLineNumber] = $this->getBounds($file->numberOfLines());
$code = [];
$line = $file->getLine($startLineNumber);
$currentLineNumber = $startLineNumber;
while ($currentLineNumber <= $endLineNumber) {
$code[$currentLineNumber] = rtrim(substr($line, 0, 250));
$line = $file->getNextLine();
$currentLineNumber++;
}
return $code;
} catch (RuntimeException $exception) {
return [];
}
}
private function getBounds($totalNumberOfLineInFile): array
{
$startLine = max($this->surroundingLine - floor($this->snippetLineCount / 2), 1);
$endLine = $startLine + ($this->snippetLineCount - 1);
if ($endLine > $totalNumberOfLineInFile) {
$endLine = $totalNumberOfLineInFile;
$startLine = max($endLine - ($this->snippetLineCount - 1), 1);
}
return [$startLine, $endLine];
}
}

View File

@@ -1,41 +0,0 @@
<?php
namespace Facade\FlareClient\Stacktrace;
use SplFileObject;
class File
{
/** @var \SplFileObject */
private $file;
public function __construct(string $path)
{
$this->file = new SplFileObject($path);
}
public function numberOfLines(): int
{
$this->file->seek(PHP_INT_MAX);
return $this->file->key() + 1;
}
public function getLine(int $lineNumber = null): string
{
if (is_null($lineNumber)) {
return $this->getNextLine();
}
$this->file->seek($lineNumber - 1);
return $this->file->current();
}
public function getNextLine(): string
{
$this->file->next();
return $this->file->current();
}
}

View File

@@ -1,71 +0,0 @@
<?php
namespace Facade\FlareClient\Stacktrace;
class Frame
{
/** @var string */
private $file;
/** @var int */
private $lineNumber;
/** @var string */
private $method;
/** @var string */
private $class;
/** @var bool */
private $isApplicationFrame;
public function __construct(
string $file,
int $lineNumber,
string $method = null,
string $class = null,
bool $isApplicationFrame = false
) {
$this->file = $file;
$this->lineNumber = $lineNumber;
$this->method = $method;
$this->class = $class;
$this->isApplicationFrame = $isApplicationFrame;
}
public function toArray(): array
{
$codeSnippet = (new Codesnippet())
->snippetLineCount(31)
->surroundingLine($this->lineNumber)
->get($this->file);
return [
'line_number' => $this->lineNumber,
'method' => $this->method,
'class' => $this->class,
'code_snippet' => $codeSnippet,
'file' => $this->file,
'is_application_frame' => $this->isApplicationFrame,
];
}
public function getFile(): string
{
return $this->file;
}
public function getLinenumber(): int
{
return $this->lineNumber;
}
public function isApplicationFrame()
{
return $this->isApplicationFrame;
}
}

View File

@@ -1,126 +0,0 @@
<?php
namespace Facade\FlareClient\Stacktrace;
use Throwable;
class Stacktrace
{
/** @var \Facade\FlareClient\Stacktrace\Frame[] */
private $frames;
/** @var string */
private $applicationPath;
public static function createForThrowable(Throwable $throwable, ?string $applicationPath = null): self
{
return new static($throwable->getTrace(), $applicationPath, $throwable->getFile(), $throwable->getLine());
}
public static function create(?string $applicationPath = null): self
{
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS & ~DEBUG_BACKTRACE_PROVIDE_OBJECT);
return new static($backtrace, $applicationPath);
}
public function __construct(array $backtrace, ?string $applicationPath = null, string $topmostFile = null, string $topmostLine = null)
{
$this->applicationPath = $applicationPath;
$currentFile = $topmostFile;
$currentLine = $topmostLine;
foreach ($backtrace as $rawFrame) {
if (! $this->frameFromFlare($rawFrame) && ! $this->fileIgnored($currentFile)) {
$this->frames[] = new Frame(
$currentFile,
$currentLine,
$rawFrame['function'] ?? null,
$rawFrame['class'] ?? null,
$this->frameFileFromApplication($currentFile)
);
}
$currentFile = $rawFrame['file'] ?? 'unknown';
$currentLine = $rawFrame['line'] ?? 0;
}
$this->frames[] = new Frame(
$currentFile,
$currentLine,
'[top]'
);
}
protected function frameFromFlare(array $rawFrame): bool
{
return isset($rawFrame['class']) && strpos($rawFrame['class'], 'Facade\\FlareClient\\') === 0;
}
protected function frameFileFromApplication(string $frameFilename): bool
{
$relativeFile = str_replace('\\', DIRECTORY_SEPARATOR, $frameFilename);
if (! empty($this->applicationPath)) {
$relativeFile = array_reverse(explode($this->applicationPath ?? '', $frameFilename, 2))[0];
}
if (strpos($relativeFile, DIRECTORY_SEPARATOR . 'vendor') === 0) {
return false;
}
return true;
}
protected function fileIgnored(string $currentFile): bool
{
$currentFile = str_replace('\\', DIRECTORY_SEPARATOR, $currentFile);
$ignoredFiles = [
'/ignition/src/helpers.php',
];
foreach ($ignoredFiles as $ignoredFile) {
if (strstr($currentFile, $ignoredFile) !== false) {
return true;
}
}
return false;
}
public function firstFrame(): Frame
{
return $this->frames[0];
}
public function toArray(): array
{
return array_map(function (Frame $frame) {
return $frame->toArray();
}, $this->frames);
}
public function firstApplicationFrame(): ?Frame
{
foreach ($this->frames as $index => $frame) {
if ($frame->isApplicationFrame()) {
return $frame;
}
}
return null;
}
public function firstApplicationFrameIndex(): ?int
{
foreach ($this->frames as $index => $frame) {
if ($frame->isApplicationFrame()) {
return $index;
}
}
return null;
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Facade\FlareClient\Time;
use DateTimeImmutable;
class SystemTime implements Time
{
public function getCurrentTime(): int
{
return (new DateTimeImmutable())->getTimestamp();
}
}

View File

@@ -1,8 +0,0 @@
<?php
namespace Facade\FlareClient\Time;
interface Time
{
public function getCurrentTime(): int;
}

View File

@@ -1,14 +0,0 @@
<?php
namespace Facade\FlareClient\Truncation;
abstract class AbstractTruncationStrategy implements TruncationStrategy
{
/** @var ReportTrimmer */
protected $reportTrimmer;
public function __construct(ReportTrimmer $reportTrimmer)
{
$this->reportTrimmer = $reportTrimmer;
}
}

View File

@@ -1,41 +0,0 @@
<?php
namespace Facade\FlareClient\Truncation;
class ReportTrimmer
{
protected static $maxPayloadSize = 524288;
protected $strategies = [
TrimStringsStrategy::class,
TrimContextItemsStrategy::class,
];
public function trim(array $payload): array
{
foreach ($this->strategies as $strategy) {
if (! $this->needsToBeTrimmed($payload)) {
break;
}
$payload = (new $strategy($this))->execute($payload);
}
return $payload;
}
public function needsToBeTrimmed(array $payload): bool
{
return strlen(json_encode($payload)) > self::getMaxPayloadSize();
}
public static function getMaxPayloadSize(): int
{
return self::$maxPayloadSize;
}
public static function setMaxPayloadSize(int $maxPayloadSize): void
{
self::$maxPayloadSize = $maxPayloadSize;
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace Facade\FlareClient\Truncation;
class TrimContextItemsStrategy extends AbstractTruncationStrategy
{
public static function thresholds()
{
return [100, 50, 25, 10];
}
public function execute(array $payload): array
{
foreach (static::thresholds() as $threshold) {
if (! $this->reportTrimmer->needsToBeTrimmed($payload)) {
break;
}
$payload['context'] = $this->iterateContextItems($payload['context'], $threshold);
}
return $payload;
}
protected function iterateContextItems(array $contextItems, int $threshold): array
{
array_walk($contextItems, [$this, 'trimContextItems'], $threshold);
return $contextItems;
}
protected function trimContextItems(&$value, $key, int $threshold)
{
if (is_array($value)) {
if (count($value) > $threshold) {
$value = array_slice($value, $threshold * -1, $threshold);
}
array_walk($value, [$this, 'trimContextItems'], $threshold);
}
return $value;
}
}

View File

@@ -1,35 +0,0 @@
<?php
namespace Facade\FlareClient\Truncation;
class TrimStringsStrategy extends AbstractTruncationStrategy
{
public static function thresholds()
{
return [1024, 512, 256];
}
public function execute(array $payload): array
{
foreach (static::thresholds() as $threshold) {
if (! $this->reportTrimmer->needsToBeTrimmed($payload)) {
break;
}
$payload = $this->trimPayloadString($payload, $threshold);
}
return $payload;
}
protected function trimPayloadString(array $payload, int $threshold): array
{
array_walk_recursive($payload, function (&$value) use ($threshold) {
if (is_string($value) && strlen($value) > $threshold) {
$value = substr($value, 0, $threshold);
}
});
return $payload;
}
}

View File

@@ -1,8 +0,0 @@
<?php
namespace Facade\FlareClient\Truncation;
interface TruncationStrategy
{
public function execute(array $payload): array;
}

View File

@@ -1,51 +0,0 @@
<?php
namespace Facade\FlareClient;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
class View
{
/** @var string */
private $file;
/** @var array */
private $data = [];
public function __construct(string $file, array $data = [])
{
$this->file = $file;
$this->data = $data;
}
public static function create(string $file, array $data = []): self
{
return new static($file, $data);
}
private function dumpViewData($variable): string
{
$cloner = new VarCloner();
$dumper = new HtmlDumper();
$dumper->setDumpHeader('');
$output = fopen('php://memory', 'r+b');
$dumper->dump($cloner->cloneVar($variable)->withMaxDepth(1), $output, [
'maxDepth' => 1,
'maxStringLength' => 160,
]);
return stream_get_contents($output, -1, 0);
}
public function toArray()
{
return [
'file' => $this->file,
'data' => array_map([$this, 'dumpViewData'], $this->data),
];
}
}

View File

@@ -1,17 +0,0 @@
<?php
if (! function_exists('array_merge_recursive_distinct')) {
function array_merge_recursive_distinct(array &$array1, array &$array2)
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
}

View File

@@ -1,40 +0,0 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->notName('*.blade.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
'class_attributes_separation' => [
'elements' => [
'method' => 'one',
],
],
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => true,
],
'single_trait_insert_per_statement' => true,
])
->setFinder($finder);

View File

@@ -1,574 +0,0 @@
# Changelog
All notable changes to `ignition` will be documented in this file
## 2.17.5 - 2022-02-23
## What's Changed
- fix solutions section padding by @faissaloux in https://github.com/facade/ignition/pull/433
- Bump markdown-it from 9.1.0 to 12.3.2 by @dependabot in https://github.com/facade/ignition/pull/446
- Bump ajv from 6.10.2 to 6.12.6 by @dependabot in https://github.com/facade/ignition/pull/448
- Fix E_NOTICE when requesting invalid script by @cweiske in https://github.com/facade/ignition/pull/449
## New Contributors
- @faissaloux made their first contribution in https://github.com/facade/ignition/pull/433
- @cweiske made their first contribution in https://github.com/facade/ignition/pull/449
**Full Changelog**: https://github.com/facade/ignition/compare/2.17.4...2.17.5
## 2.17.4 - 2021-12-27
- fix bug where uninitialized property within a job could break Ignition
## 2.17.3 - 2021-12-23
- allow filtering route parameters using a `toFlare` method
## 2.17.2 - 2021-11-29
## What's Changed
- Allow overflow-x on solutions with unbreakable words by @willemvb in https://github.com/facade/ignition/pull/431
**Full Changelog**: https://github.com/facade/ignition/compare/2.17.1...2.17.2
## 2.17.2 - 2021-11-29
- scroll overflow on solutions
## 2.17.1 - 2021-11-25
- streamline Livewire solutions
## 2.17.0 - 2021-11-24
- improve recording of Livewire data
## 2.16.1 - 2021-11-16
- allow sending of unbinded sql queries to Flare
## 2.16.0 - 2021-10-28
- improve recording data from jobs (#416)
## 2.15.0 - 2021-10-11
- improve output of flare:test
## 2.14.1 - 2021-10-08
- update base URL for Flare
## 2.14.0 - 2021-10-01
- add support for VScode WSL + SSH remote (#420)
## 2.13.1 - 2021-09-13
- fix namespace of `SentReports` in facade
## 2.13.0 - 2021-09-13
- add tracking uuid (#418)
## 2.12.1 - 2021-09-08
- add support for VS Codium editor (#417)
## 2.12.0 - 2021-08-24
- add support for collecting information about jobs (#412)
## 2.11.4 - 2021-08-16
- use npm ci instead of install (#411)
## 2.11.3 - 2021-08-16
- fix issues with circular dependencies in model route parameters (#408)
- remove notice about dirty git state in context
- wrap `AddGitInformation` middleware in try-catch
## 2.11.2 - 2021-07-20
- fix issues introduced in 2.11.1 (#403)
## 2.11.1 - 2021-07-20
- fix sending queued reports on Laravel Vapor queues (#398)
## 2.11.0 - 2021-07-12
- prepare Laravel 9 support
- remove filp/whoops dependency
- update front-end dependencies
## 2.10.2 - 2021-06-11
- fix typo in config/flare.php (#395)
## 2.10.1 - 2021-06-03
- fix memory leaks in Octane (#393)
## 2.10.0 - 2021-06-03
- add a solution for lazy loading violations (#392)
## 2.9.0 - 2021-05-05
- add Xdebug format links for editor (#383)
## 2.8.4 - 2021-04-29
- avoid making call to Flare when no API key is specified
## 2.8.3 - 2021-04-09
- support Octane (#379)
## 2.8.2 - 2021-04-08
- censor passwords by default (#377)
## 2.8.1 - 2021-04-08
- add `censor_request_body_fields` default config option
## 2.8.0 - 2021-04-08
- add `censor_request_body_fields` config option
## 2.7.0 - 2021-03-30
- adds a debug warning when having debug enabled on a non-local environment (#366)
## 2.6.1 - 2021-03-30
- Disable executing solutions on non-local environments or from non-local IP addresses (#364)
## 2.6.0 - 2021-03-24
- add extra output to test command when executing verbosely
## 2.5.14 - 2021-03-03
- fix ignition not working when there is no argv
## 2.5.13 - 2021-02-16
- remove custom grouping
## 2.5.12 - 2021-02-15
- fix wrong config usage (#354)
## 2.5.11 - 2021-02-05
- fix memory leaks caused by log and query recorder (#344)
## 2.5.10 - 2021-02-02
- fix tinker logs not being sent to Flare
## 2.5.9 - 2021-01-26
- fix logged context not being sent to Flare
## 2.5.8 - 2020-12-29
- fix double `$` on PHP 8 (#338)
## 2.5.7 - 2020-12-29
- fix for breaking change in highlight.js (fixes 2.5.5)
## 2.5.6 - 2020-12-29
- revert to compiled js of 2.5.3
## 2.5.5 - 2020-12-29
- added compiled js of previous release
## 2.5.4 - 2020-12-29
- added support for Nova text editor (#343)
## 2.5.3 - 2020-12-08
- Use Livewire compatible compiler engine when using Livewire (#340)
## 2.5.2 - 2020-11-14
- fix `MakeViewVariableOptionalSolution` to disallow stream wrappers and files that do not end in ".blade.php" (#334)
## 2.5.1 - 2020-11-13
- add support for LiveWire component urls
## 2.5.0 - 2020-10-27
- add PHP 8.0-dev support
- remove unnecessary `scrivo/highlight.php` dependency
## 2.4.2 - 2021-03-08
- fix `MakeViewVariableOptionalSolution` to disallow stream wrappers and files that do not end in .blade.php (#356)
## 2.4.1 - 2020-10-14
- fix copy casing
## 2.4.0 - 2020-10-14
- add livewire component discovery solution
## 2.3.8 - 2020-10-02
- Address Missing Mix Manifest Error (#317)
## 2.3.7 - 2020-09-06
- add loading state on share button (#309)
- compatibility fix for L8
## 2.3.6 - 2020-08-10
- possible security vulnerability: bump elliptic version (#300)
- possible XSS vulnerability: escape characters in stacktrace and exception title
## 2.3.5 - 2020-08-01
- catch exception in detectLineNumber for not existing blade files (#299)
## 2.3.4 - 2020-07-27
- fix an error that would throw a blank page when using third party extensions
## 2.3.3 -2020-07-14
- fix all psalm related issues
## 2.3.2 - 2020-07-14
- properly bind singleton (#291)
## 2.3.1 - 2020-07-13
- improve db name solution (#289)
## 2.3.0 - 2020-07-13
- allow override of Dumper via `$_SERVER variable` (#271)
- make DumpHandler instance manually in DumpRecorder (#286)
- only setup queues when queue is available (#287)
## 2.2.0 - 2020-07-13
- add `ignition:make:solution-provider` command
## 2.1.0 - 2020-07-13
- add "Undefined Property" solution (#264)
## 2.0.10 - 2020-07-13
- correctly detect dump location from ddd (#216)
## 2.0.9 - 2020-07-13
- use application contract instead of concrete class (#243)
## 2.0.8 - 2020-07-12
- do not render solution title tag for empty titles
## 2.0.7 - 2020-06-07
- Fix `DefaultDbNameSolutionProvider` (#277)
## 2.0.6 - 2020-06-01
- remove ability to fix variable names
## 2.0.5 - 2020-05-29
- blacklist certain variable names when fixing variable names
## 2.0.4 - 2020-05-18
- handle exceptions in case the request doesn't have a user (#274)
## 2.0.3 - 2020-04-07
- support Laravel 8
## 2.0.2 - 2020-03-18
- fix execute solution route not defined (#265)
## 2.0.0 - 2020-02-02
- adds support for Laravel 7
- drop support for Laravel 6 and below
- git information won't be collected by default anymore (if you need this set `collect_git_information` to `true` in the `flare` config file)
- `MissingPackageSolutionProvider` was added to the `ignored_solution_providers` because it potentially could be slow.
## 1.16.0 - 2020-01-21
- add named routes (#197)
## 1.15.0 - 2020-01-21
- add exception to the bottom of the html (#230)
## 1.14.0 - 2020-01-06
- add indicator that solution is running (#212)
## 1.13.1 - 2020-01-02
- Remove external reference for icons (#134)
## 1.13.0 - 2019-11-27
- Allow custom grouping types
## 1.12.1 - 2019-11-25
- Detect multibyte position offsets when adding linenumbers to the blade view - Fixes #193
## 1.12.0 - 2019-11-14
- Add exception to html (#206)
- Add a clear exception when passing no parameters to ddd (#205)
- Ignore JS tests (#215)
- Fix share report route bug
## 1.11.2 - 2019-10-13
- simplify default Laravel installation (#198)
## 1.11.1 - 2019-10-08
- add conditional line number (#182)
## 1.11.0 - 2019-10-08
- add better error messages for missing validation rules (#125)
## 1.10.0 - 2019-10-07
- Add `ignition:make-solution` command
- Add default for query binding option (Fixes #183)
## 1.9.2 - 2019-10-04
- Fix service provider registration (Fixes #177)
## 1.9.1 - 2019-10-01
- collapse vendor frames on windows fix (#176)
## 1.9.0 - 2019-09-27
- add ability to send logs to flare
- add `ddd` function
## 1.8.4 - 2019-09-27
- Resolve configuration from the injected app instead of the helper ([#168](https://github.com/facade/ignition/pull/168))
## 1.8.3 - 2019-09-25
- Remove `select-none` from error message
- Change line clamp behaviour for longer error messages
## 1.8.2 - 2019-09-20
- fix for `TypeError: Cannot set property 'highlightState' of undefined`
## 1.8.1 - 2019-09-20
- Revert javascript assets via URL - Fixes #161
## 1.8.0 - 2019-09-18
- added solution for running Laravel Dusk in production ([#121](https://github.com/facade/ignition/pull/121))
- Automatically fix blade variable typos and optional variables ([#38](https://github.com/facade/ignition/pull/38))
## 1.7.1 - 2019-09-18
- Use url helper to generate housekeeping endpoints
## 1.7.0 - 2019-09-18
- Add the ability to define a query collector max value ([#153](https://github.com/facade/ignition/pull/153))
## 1.6.10 - 2019-09-18
- fix `__invoke` method name in solution ([#151](https://github.com/facade/ignition/pull/151))
## 1.6.9 - 2019-09-18
- Add noscript trace information - fixes [#146](https://github.com/facade/ignition/issues/146)
## 1.6.8 - 2019-09-18
- Use javascript content type for asset response - fixes [#149](https://github.com/facade/ignition/issues/149)
## 1.6.7 - 2019-09-18
- Load javascript assets via URL. Fixes [#16](https://github.com/facade/ignition/issues/16)
## 1.6.6 - 2019-09-16
- Prevent undefined index exception in `TestCommand`
## 1.6.5 - 2019-09-13
- Ignore invalid characters in JSON encoding. Fixes [#138](https://github.com/facade/ignition/issues/138)
## 1.6.4 - 2019-09-13
- add no-index on error page
## 1.6.3 - 2019-09-12
- Fix `RouteNotDefinedSolutionProvider` in Laravel 5
## 1.6.2 - 2019-09-12
- updated publishing tag from default config
## 1.6.1 - 2019-09-12
- Resolve configuration from the injected application instead of the helper - Fixes [#131](https://github.com/facade/ignition/issues/131)
## 1.6.0 - 2019-09-09
- add `RouteNotDefined` solution provider ([#113](https://github.com/facade/ignition/pull/113))
## 1.5.0 - 2019-09-09
- suggest running migrations when a column is missing ([#83](https://github.com/facade/ignition/pull/83))
## 1.4.19 - 2019-09-09
- Remove quotation from git commit url ([#89](https://github.com/facade/ignition/pull/89))
## 1.4.18 - 2019-09-09
- Fix open_basedir restriction when looking up config file. Fixes ([#120](https://github.com/facade/ignition/pull/120))
## 1.4.17 - 2019-09-06
- Remove Inter, Operator from font stack. Fixes [#74](https://github.com/facade/ignition/issues/74)
## 1.4.15 - 2019-09-05
- Use previous exception trace for view exceptions. Fixes [#107](https://github.com/facade/ignition/issues/107)
## 1.4.14 - 2019-09-05
- Use DIRECTORY_SEPARATOR to fix an issue with blade view lookups in Windows
## 1.4.13 - 2019-09-05
- Use Laravel style comments
## 1.4.12 - 2019-09-04
- Use a middleware to protect ignition routes ([#93](https://github.com/facade/ignition/pull/93))
## 1.4.11 - 2019-09-04
- Use exception line number as fallbacks for view errors
## 1.4.10 - 2019-09-04
- Wrap solution provider lookup in a try-catch block
## 1.4.9 - 2019-09-04
- Lookup the first exception when linking to Telescope
## 1.4.8 - 2019-09-04
- pass an empty string to query if no connection name is available - fixes [#86](https://github.com/facade/ignition/issues/86)
## 1.4.7 - 2019-09-04
- Match whoops minimum version constraint with Laravel 6
## 1.4.6 - 2019-09-04
- Use empty array for default ignored solution providers
## 1.4.5 - 2019-09-03
- fix for new Laravel 6 installs
## 1.4.4 - 2019-09-03
- Suggest default database name in Laravel 6
- Add void return type to FlareHandler::write()
## 1.4.3 - 2019-09-03
- allow monolog v2
## 1.4.2 - 2019-09-03
- style fixes
## 1.4.1 - 2019-09-03
- Change `remote-sites-path` and `local-sites-path` config keys to us snake case
## 1.4.0 - 2019-09-03
- add `enable_runnable_solutions` key to config file
## 1.3.0 - 2019-09-02
- add `MergeConflictSolutionProvider`
## 1.2.0 - 2019-09-02
- add `ignored_solution_providers` key to config file
## 1.1.1 - 2019-09-02
- Fixed context tab crash when not using git ([#24](https://github.com/facade/ignition/issues/24))
## 1.1.0 - 2019-09-02
- Fixed an error that removed the ability to register custom blade directives.
- Fixed an error that prevented solution execution in Laravel 5.5 and 5.6
- The "Share" button can now be disabled in the configuration file
- Fixes an error when trying to log `null` values
## 1.0.4 - 2019-09-02
- Check if the authenticated user has a `toArray` method available, before collecting user data
## 1.0.3 - 2019-09-02
- Corrected invalid link in config file
## 1.0.2 - 2019-09-02
- Fixed an error in the `DefaultDbNameSolutionProvider` that could cause an infinite loop in Laravel < 5.6.28
## 1.0.1 - 2019-08-31
- add support for L5.5 & 5.6 ([#21](https://github.com/facade/ignition/pull/21))
## 1.0.0 - 2019-08-30
- initial release

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) Facade <info@facade.company>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,30 +0,0 @@
# Ignition: a beautiful error page for Laravel apps
[![Latest Version on Packagist](https://img.shields.io/packagist/v/facade/ignition.svg?style=flat-square)](https://packagist.org/packages/facade/ignition)
![Tests](https://github.com/facade/ignition/workflows/Run%20tests/badge.svg)
[![Total Downloads](https://img.shields.io/packagist/dt/facade/ignition.svg?style=flat-square)](https://packagist.org/packages/facade/ignition)
[Ignition](https://flareapp.io/docs/ignition-for-laravel/introduction) is a beautiful and customizable error page for Laravel applications running on Laravel 5.5 up Laravel 8. It is the default error page for all Laravel 6 applications. It also allows to publicly share your errors on [Flare](https://flareapp.io). If configured with a valid Flare API key, your errors in production applications will be tracked, and you'll get notified when they happen.
## Using Laravel 8 or above?
If you're on Laravel 8 or above, you can switch to [spatie/laravel-ignition](https://github.com/spatie/laravel-ignition), which is a drop-in replacement.
Replace `facade/ignition` with `"spatie/laravel-ignition": "^1.0"` in your application's `composer.json` file.
Going forward, we'll only add security fixes to facade/ignition and highly encourage you to switch to spatie/laravel-ignition.
## Official Documentation
The official documentation for Ignition can be found on the [Flare website](https://flareapp.io/docs/ignition-for-laravel/installation).
### Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

View File

@@ -1,3 +0,0 @@
# Security Policy
For security related problems, please don't use the public issue tracker, but mail info@spatie.be.

View File

@@ -1,77 +0,0 @@
{
"name": "facade/ignition",
"description": "A beautiful error page for Laravel applications.",
"keywords": [
"error",
"page",
"laravel",
"flare"
],
"homepage": "https://github.com/facade/ignition",
"license": "MIT",
"require": {
"php": "^7.2.5|^8.0",
"ext-json": "*",
"ext-mbstring": "*",
"facade/flare-client-php": "^1.9.1",
"facade/ignition-contracts": "^1.0.2",
"illuminate/support": "^7.0|^8.0",
"monolog/monolog": "^2.0",
"symfony/console": "^5.0",
"symfony/var-dumper": "^5.0",
"ext-curl": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"livewire/livewire": "^2.4",
"mockery/mockery": "^1.3",
"orchestra/testbench": "^5.0|^6.0",
"psalm/plugin-laravel": "^1.2"
},
"suggest": {
"laravel/telescope": "^3.1"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
},
"laravel": {
"providers": [
"Facade\\Ignition\\IgnitionServiceProvider"
],
"aliases": {
"Flare": "Facade\\Ignition\\Facades\\Flare"
}
}
},
"autoload": {
"psr-4": {
"Facade\\Ignition\\": "src"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Facade\\Ignition\\Tests\\": "tests"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"psalm": "vendor/bin/psalm",
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes",
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage"
},
"support": {
"issues": "https://github.com/facade/ignition/issues",
"forum": "https://twitter.com/flareappio",
"source": "https://github.com/facade/ignition",
"docs": "https://flareapp.io/docs/ignition-for-laravel/introduction"
}
}

View File

@@ -1,62 +0,0 @@
<?php
return [
/*
|
|--------------------------------------------------------------------------
| Flare API key
|--------------------------------------------------------------------------
|
| Specify Flare's API key below to enable error reporting to the service.
|
| More info: https://flareapp.io/docs/general/projects
|
*/
'key' => env('FLARE_KEY'),
/*
|--------------------------------------------------------------------------
| Reporting Options
|--------------------------------------------------------------------------
|
| These options determine which information will be transmitted to Flare.
|
*/
'reporting' => [
'anonymize_ips' => true,
'collect_git_information' => false,
'report_queries' => true,
'maximum_number_of_collected_queries' => 200,
'report_query_bindings' => true,
'report_view_data' => true,
'grouping_type' => null,
'report_logs' => true,
'maximum_number_of_collected_logs' => 200,
'censor_request_body_fields' => ['password'],
],
/*
|--------------------------------------------------------------------------
| Reporting Log statements
|--------------------------------------------------------------------------
|
| If this setting is `false` log statements won't be sent as events to Flare,
| no matter which error level you specified in the Flare log channel.
|
*/
'send_logs_as_events' => true,
/*
|--------------------------------------------------------------------------
| Censor request body fields
|--------------------------------------------------------------------------
|
| These fields will be censored from your request when sent to Flare.
|
*/
'censor_request_body_fields' => ['password'],
];

View File

@@ -1,126 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking any edit button.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug"
|
*/
'editor' => env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Theme
|--------------------------------------------------------------------------
|
| Here you may specify which theme Ignition should use.
|
| Supported: "light", "dark", "auto"
|
*/
'theme' => env('IGNITION_THEME', 'light'),
/*
|--------------------------------------------------------------------------
| Sharing
|--------------------------------------------------------------------------
|
| You can share local errors with colleagues or others around the world.
| Sharing is completely free and doesn't require an account on Flare.
|
| If necessary, you can completely disable sharing below.
|
*/
'enable_share_button' => env('IGNITION_SHARING_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Register Ignition commands
|--------------------------------------------------------------------------
|
| Ignition comes with an additional make command that lets you create
| new solution classes more easily. To keep your default Laravel
| installation clean, this command is not registered by default.
|
| You can enable the command registration below.
|
*/
'register_commands' => env('REGISTER_IGNITION_COMMANDS', false),
/*
|--------------------------------------------------------------------------
| Ignored Solution Providers
|--------------------------------------------------------------------------
|
| You may specify a list of solution providers (as fully qualified class
| names) that shouldn't be loaded. Ignition will ignore these classes
| and possible solutions provided by them will never be displayed.
|
*/
'ignored_solution_providers' => [
\Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider::class,
],
/*
|--------------------------------------------------------------------------
| Runnable Solutions
|--------------------------------------------------------------------------
|
| Some solutions that Ignition displays are runnable and can perform
| various tasks. Runnable solutions are enabled when your app has
| debug mode enabled. You may also fully disable this feature.
|
*/
'enable_runnable_solutions' => env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS', null),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Ignition will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('IGNITION_REMOTE_SITES_PATH', ''),
'local_sites_path' => env('IGNITION_LOCAL_SITES_PATH', ''),
/*
|--------------------------------------------------------------------------
| Housekeeping Endpoint Prefix
|--------------------------------------------------------------------------
|
| Ignition registers a couple of routes when it is enabled. Below you may
| specify a route prefix that will be used to host all internal links.
|
*/
'housekeeping_endpoint_prefix' => '_ignition',
];

View File

@@ -1,65 +0,0 @@
{
"private": true,
"scripts": {
"dev": "webpack --mode development --watch",
"build": "NODE_ENV=production webpack --mode production",
"format": "prettier --write 'resources/**/*.{css,js,ts,vue}'"
},
"dependencies": {
"git-url-parse": "^11.1.2",
"highlight.js": "^10.4.1",
"lodash": "^4.17.21",
"markdown-it": "^12.3.2",
"md5": "^2.2.1",
"sql-formatter": "^2.3.3"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/preset-typescript": "^7.3.3",
"@fullhuman/postcss-purgecss": "^1.1.0",
"@types/jest": "^24.0.15",
"@types/lodash": "^4.14.133",
"babel-loader": "^8.0.6",
"css-loader": "^3.0.0",
"husky": "^1.3.1",
"jest": "^24.8.0",
"lint-staged": "^8.1.5",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.6.0",
"prettier": "^1.16.4",
"style-loader": "^0.23.1",
"tailwindcss": "^1.0.4",
"typescript": "^3.5.2",
"vue": "^2.6.10",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && yarn build && git add resources/compiled/ignition.js"
}
},
"lint-staged": {
"linters": {
"*.{css,js,ts,vue}": [
"yarn format",
"git add"
]
},
"ignore": [
"resources/compiled/**/*"
]
},
"jest": {
"testPathIgnorePatterns": [
"/node_modules/",
"/__helpers__/"
]
}
}

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="3.17.1@8f211792d813e4dc89f04ed372785ce93b902fd1">
<file src="src/IgnitionServiceProvider.php">
<UndefinedInterfaceMethod occurrences="7">
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
</UndefinedInterfaceMethod>
</file>
<file src="src/LogRecorder/LogRecorder.php">
<UndefinedInterfaceMethod occurrences="1">
<code>$this-&gt;app</code>
</UndefinedInterfaceMethod>
</file>
<file src="src/QueryRecorder/QueryRecorder.php">
<UndefinedInterfaceMethod occurrences="3">
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
<code>$this-&gt;app</code>
</UndefinedInterfaceMethod>
</file>
<file src="src/SolutionProviders/MissingLivewireComponentSolutionProvider.php">
<UndefinedClass occurrences="1">
<code>ComponentNotFoundException</code>
</UndefinedClass>
</file>
<file src="src/SolutionProviders/UnknownValidationSolutionProvider.php">
<UndefinedClass occurrences="1">
<code>app('validator')</code>
</UndefinedClass>
</file>
<file src="src/Solutions/LivewireDiscoverSolution.php">
<UndefinedClass occurrences="1">
<code>LivewireComponentsFinder</code>
</UndefinedClass>
</file>
<file src="src/Views/Engines/CompilerEngine.php">
<ParamNameMismatch occurrences="1">
<code>$baseException</code>
</ParamNameMismatch>
</file>
<file src="src/Views/Engines/PhpEngine.php">
<ParamNameMismatch occurrences="1">
<code>$baseException</code>
</ParamNameMismatch>
</file>
</files>

View File

@@ -1,47 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
findUnusedVariablesAndParams="true"
resolveFromConfigFile="true"
useDocblockPropertyTypes="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src"/>
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<UndefinedInterfaceMethod>
<errorLevel type="suppress">
<file name="src/Solutions/SolutionTransformer.php" />
</errorLevel>
</UndefinedInterfaceMethod>
<ForbiddenCode>
<errorLevel type="suppress">
<file name="src/SolutionProviders/MergeConflictSolutionProvider.php" />
</errorLevel>
</ForbiddenCode>
<InvalidCast>
<errorLevel type="suppress">
<file name="src/DumpRecorder/DumpRecorder.php" />
</errorLevel>
</InvalidCast>
<UndefinedClass>
<errorLevel type="suppress">
<file name="src/ErrorPage/ErrorPageViewModel.php" />
<file name="src/ErrorPage/IgnitionExceptionRenderer.php" />
<file name="src/IgnitionServiceProvider.php" />
</errorLevel>
</UndefinedClass>
</issueHandlers>
<plugins>
<pluginClass class="Psalm\LaravelPlugin\Plugin"/>
</plugins>
</psalm>

View File

@@ -1,3 +0,0 @@
compiled/*
!compiled/index.html
!compiled/ignition.js

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Vue App</title>
<link href="/flare.js" rel="preload" as="script"></head>
<body>
<div id="app"></div>
<script type="text/javascript" src="/flare.js"></script></body>
</html>

View File

@@ -1,65 +0,0 @@
<!doctype html>
<html class="theme-<?=$config['theme']?>">
<!--
<?=$throwableString?>
-->
<head>
<!-- Hide dumps asap -->
<style>
pre.sf-dump {
display: none !important;
}
</style>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="robots" content="noindex, nofollow">
<title><?= $title ?></title>
<?php foreach ($styles as $script): ?>
<link rel="stylesheet" href="<?=$housekeepingEndpoint?>/styles/<?=$script?>">
<?php endforeach; ?>
</head>
<body class="scrollbar-lg">
<script>
window.data = <?=
$jsonEncode([
'report' => $report,
'config' => $config,
'solutions' => $solutions,
'telescopeUrl' => $telescopeUrl,
'shareEndpoint' => $shareEndpoint,
'defaultTab' => $defaultTab,
'defaultTabProps' => $defaultTabProps,
'appEnv' => $appEnv,
'appDebug' => $appDebug,
])
?>;
window.tabs = <?=$tabs?>;
</script>
<noscript><pre><?=$throwableString?></pre></noscript>
<div id="app"></div>
<script><?= $getAssetContents('ignition.js') ?></script>
<script>
window.Ignition = window.ignite(window.data);
</script>
<?php foreach ($scripts as $script): ?>
<script src="<?=$housekeepingEndpoint?>/scripts/<?=$script?>"></script>
<?php endforeach; ?>
<script>
Ignition.start();
</script>
<!--
<?=$throwableString?>
-->
</body>
</html>

View File

@@ -1,168 +0,0 @@
<?php
namespace Facade\Ignition\Actions;
use Exception;
use Facade\FlareClient\Http\Client;
use Facade\FlareClient\Truncation\ReportTrimmer;
use Facade\Ignition\Exceptions\UnableToShareErrorException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
class ShareReportAction
{
/** @var array */
protected $tabs;
/** @var \Facade\FlareClient\Http\Client */
protected $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function handle(array $report, array $tabs, ?string $lineSelection = null)
{
$this->tabs = $tabs;
$report = $this->filterReport($report);
try {
return $this->client->post('public-reports', [
'report' => $this->trimReport($report),
'tabs' => $tabs,
'lineSelection' => $lineSelection,
]);
} catch (Exception $exception) {
throw new UnableToShareErrorException($exception->getMessage());
}
}
public function filterReport(array $report): array
{
if (! $this->hasTab('stackTraceTab')) {
$report['stacktrace'] = array_slice($report['stacktrace'], 0, 1);
}
if (! $this->hasTab('debugTab')) {
$report['glows'] = [];
}
$report['context'] = $this->filterContextItems($report['context']);
return $report;
}
protected function hasTab(string $tab): bool
{
return in_array($tab, $this->tabs);
}
protected function filterContextItems(array $contextItems): array
{
if (! $this->hasTab('requestTab')) {
$contextItems = $this->removeRequestInformation($contextItems);
}
if (! $this->hasTab('appTab')) {
$contextItems = $this->removeAppInformation($contextItems);
}
if (! $this->hasTab('userTab')) {
$contextItems = $this->removeUserInformation($contextItems);
}
if (! $this->hasTab('contextTab')) {
$contextItems = $this->removeContextInformation($contextItems);
}
if (! $this->hasTab('debugTab')) {
$contextItems = $this->removeDebugInformation($contextItems);
}
return $contextItems;
}
protected function removeRequestInformation(array $contextItems): array
{
Arr::forget($contextItems, 'request');
Arr::forget($contextItems, 'request_data');
Arr::forget($contextItems, 'headers');
Arr::forget($contextItems, 'session');
Arr::forget($contextItems, 'cookies');
return $contextItems;
}
protected function removeAppInformation(array $contextItems): array
{
Arr::forget($contextItems, 'view');
Arr::forget($contextItems, 'route');
return $contextItems;
}
protected function removeUserInformation(array $contextItems): array
{
Arr::forget($contextItems, 'user');
Arr::forget($contextItems, 'request.ip');
Arr::forget($contextItems, 'request.useragent');
return $contextItems;
}
protected function removeContextInformation(array $contextItems): array
{
Arr::forget($contextItems, 'env');
Arr::forget($contextItems, 'git');
Arr::forget($contextItems, 'context');
Arr::forget($contextItems, $this->getCustomContextGroups($contextItems));
return $contextItems;
}
protected function removeDebugInformation(array $contextItems): array
{
Arr::forget($contextItems, 'dumps');
Arr::forget($contextItems, 'glows');
Arr::forget($contextItems, 'logs');
Arr::forget($contextItems, 'queries');
return $contextItems;
}
protected function getCustomContextGroups(array $contextItems): array
{
$predefinedContextItemGroups = [
'request',
'request_data',
'headers',
'session',
'cookies',
'view',
'queries',
'route',
'user',
'env',
'git',
'context',
'logs',
'dumps',
'exception',
];
return Collection::make($contextItems)
->reject(function ($_value, $group) use ($predefinedContextItemGroups) {
return in_array($group, $predefinedContextItemGroups);
})
->keys()
->toArray();
}
protected function trimReport(array $report): array
{
return (new ReportTrimmer())->trim($report);
}
}

View File

@@ -1,65 +0,0 @@
<?php
namespace Facade\Ignition\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class SolutionMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'ignition:make-solution';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new custom Ignition solution class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Solution';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return $this->option('runnable')
? __DIR__.'/stubs/runnable-solution.stub'
: __DIR__.'/stubs/solution.stub';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Solutions';
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['runnable', null, InputOption::VALUE_NONE, 'Create runnable solution'],
];
}
}

View File

@@ -1,50 +0,0 @@
<?php
namespace Facade\Ignition\Commands;
use Illuminate\Console\GeneratorCommand;
class SolutionProviderMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'ignition:make-solution-provider';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new custom Ignition solution provider class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Solution Provider';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/solution-provider.stub';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\SolutionProviders';
}
}

View File

@@ -1,125 +0,0 @@
<?php
namespace Facade\Ignition\Commands;
use Composer\InstalledVersions;
use Exception;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Http\Exceptions\BadResponseCode;
use Illuminate\Config\Repository;
use Illuminate\Console\Command;
use Illuminate\Log\LogManager;
class TestCommand extends Command
{
protected $signature = 'flare:test';
protected $description = 'Send a test notification to Flare';
/** @var \Illuminate\Config\Repository */
protected $config;
public function handle(Repository $config)
{
$this->config = $config;
$this->checkFlareKey();
if (app()->make('log') instanceof LogManager) {
$this->checkFlareLogger();
}
$this->sendTestException();
}
protected function checkFlareKey()
{
$message = empty($this->config->get('flare.key'))
? '❌ Flare key not specified. Make sure you specify a value in the `key` key of the `flare` config file.'
: '✅ Flare key specified';
$this->info($message);
return $this;
}
public function checkFlareLogger()
{
$defaultLogChannel = $this->config->get('logging.default');
$activeStack = $this->config->get("logging.channels.{$defaultLogChannel}");
if (is_null($activeStack)) {
$this->info("❌ The default logging channel `{$defaultLogChannel}` is not configured in the `logging` config file");
}
if (! isset($activeStack['channels']) || ! in_array('flare', $activeStack['channels'])) {
$this->info("❌ The logging channel `{$defaultLogChannel}` does not contain the 'flare' channel");
}
if (is_null($this->config->get('logging.channels.flare'))) {
$this->info('❌ There is no logging channel named `flare` in the `logging` config file');
}
if ($this->config->get('logging.channels.flare.driver') !== 'flare') {
$this->info('❌ The `flare` logging channel defined in the `logging` config file is not set to `flare`.');
}
$this->info('✅ The Flare logging driver was configured correctly.');
return $this;
}
protected function sendTestException()
{
$testException = new Exception('This is an exception to test if the integration with Flare works.');
try {
app(Flare::class)->sendTestReport($testException);
$this->info('');
} catch (Exception $exception) {
$this->warn('❌ We were unable to send an exception to Flare. ');
if ($exception instanceof BadResponseCode) {
$this->info('');
$message = 'Unknown error';
$body = $exception->response->getBody();
if (is_array($body) && isset($body['message'])) {
$message = $body['message'];
}
$this->warn("{$exception->response->getHttpResponseCode()} - {$message}");
} else {
$this->warn($exception->getMessage());
}
$this->warn('Make sure that your key is correct and that you have a valid subscription.');
$this->info('');
$this->info('For more info visit the docs on https://flareapp.io/docs/ignition-for-laravel/introduction');
$this->info('You can see the status page of Flare at https://status.flareapp.io');
$this->info('Flare support can be reached at support@flareapp.io');
$this->line('');
$this->line('Extra info');
$this->table([], [
['Platform', PHP_OS],
['PHP', phpversion()],
['Laravel', app()->version()],
['facade/ignition', InstalledVersions::getVersion('facade/ignition')],
['facade/flare-client-php', InstalledVersions::getVersion('facade/flare-client-php')],
['Curl', curl_version()['version']],
['SSL', curl_version()['ssl_version']],
]);
if ($this->output->isVerbose()) {
throw $exception;
}
return;
}
$this->info('We tried to send an exception to Flare. Please check if it arrived!');
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace DummyNamespace;
use Facade\IgnitionContracts\RunnableSolution;
class DummyClass implements RunnableSolution
{
public function getSolutionTitle(): string
{
return '';
}
public function getDocumentationLinks(): array
{
return [];
}
public function getSolutionActionDescription(): string
{
return '';
}
public function getRunButtonText(): string
{
return '';
}
public function getSolutionDescription(): string
{
return '';
}
public function getRunParameters(): array
{
return [];
}
public function run(array $parameters = [])
{
//
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace DummyNamespace;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
class DummyClass implements HasSolutionsForThrowable
{
public function canSolve(): bool
{
return false;
}
public function getSolutions(): array
{
return [];
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace DummyNamespace;
use Facade\IgnitionContracts\Solution;
class DummyClass implements Solution
{
public function getSolutionTitle(): string
{
return '';
}
public function getSolutionDescription(): string
{
return '';
}
public function getDocumentationLinks(): array
{
return [];
}
}

View File

@@ -1,9 +0,0 @@
<?php
namespace Facade\Ignition\Context;
use Facade\FlareClient\Context\ConsoleContext;
class LaravelConsoleContext extends ConsoleContext
{
}

View File

@@ -1,31 +0,0 @@
<?php
namespace Facade\Ignition\Context;
use Facade\FlareClient\Context\ContextDetectorInterface;
use Facade\FlareClient\Context\ContextInterface;
use Illuminate\Http\Request;
use Livewire\LivewireManager;
class LaravelContextDetector implements ContextDetectorInterface
{
public function detectCurrentContext(): ContextInterface
{
if (app()->runningInConsole()) {
return new LaravelConsoleContext($_SERVER['argv'] ?? []);
}
$request = app(Request::class);
if ($this->isRunningLiveWire($request)) {
return new LivewireRequestContext($request, app(LivewireManager::class));
}
return new LaravelRequestContext($request);
}
protected function isRunningLiveWire(Request $request)
{
return $request->hasHeader('x-livewire') && $request->hasHeader('referer');
}
}

View File

@@ -1,85 +0,0 @@
<?php
namespace Facade\Ignition\Context;
use Facade\FlareClient\Context\RequestContext;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Throwable;
class LaravelRequestContext extends RequestContext
{
/** @var \Illuminate\Http\Request */
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function getUser(): array
{
try {
$user = $this->request->user();
if (! $user) {
return [];
}
} catch (Throwable $e) {
return [];
}
try {
if (method_exists($user, 'toFlare')) {
return $user->toFlare();
}
if (method_exists($user, 'toArray')) {
return $user->toArray();
}
} catch (Throwable $e) {
return [];
}
return [];
}
public function getRoute(): array
{
$route = $this->request->route();
return [
'route' => optional($route)->getName(),
'routeParameters' => $this->getRouteParameters(),
'controllerAction' => optional($route)->getActionName(),
'middleware' => array_values(optional($route)->gatherMiddleware() ?? []),
];
}
protected function getRouteParameters(): array
{
try {
return collect(optional($this->request->route())->parameters ?? [])
->map(function ($parameter) {
return $parameter instanceof Model ? $parameter->withoutRelations() : $parameter;
})
->map(function ($parameter) {
return method_exists($parameter, 'toFlare') ? $parameter->toFlare() : $parameter;
})
->toArray();
} catch (Throwable $e) {
return [];
}
}
public function toArray(): array
{
$properties = parent::toArray();
$properties['route'] = $this->getRoute();
$properties['user'] = $this->getUser();
return $properties;
}
}

View File

@@ -1,94 +0,0 @@
<?php
namespace Facade\Ignition\Context;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Livewire\LivewireManager;
class LivewireRequestContext extends LaravelRequestContext
{
/** @var \Livewire\LivewireManager */
protected $livewireManager;
public function __construct(
Request $request,
LivewireManager $livewireManager
) {
parent::__construct($request);
$this->livewireManager = $livewireManager;
}
public function getRequest(): array
{
$properties = parent::getRequest();
$properties['method'] = $this->livewireManager->originalMethod();
$properties['url'] = $this->livewireManager->originalUrl();
return $properties;
}
public function toArray(): array
{
$properties = parent::toArray();
$properties['livewire'] = $this->getLiveWireInformation();
return $properties;
}
protected function getLiveWireInformation(): array
{
$componentId = $this->request->input('fingerprint.id');
$componentAlias = $this->request->input('fingerprint.name');
if ($componentAlias === null) {
return [];
}
try {
$componentClass = $this->livewireManager->getClass($componentAlias);
} catch (Exception $e) {
$componentClass = null;
}
return [
'component_class' => $componentClass,
'component_alias' => $componentAlias,
'component_id' => $componentId,
'data' => $this->resolveData(),
'updates' => $this->resolveUpdates(),
];
}
protected function resolveData(): array
{
$data = $this->request->input('serverMemo.data') ?? [];
$dataMeta = $this->request->input('serverMemo.dataMeta') ?? [];
foreach ($dataMeta['modelCollections'] ?? [] as $key => $value) {
$data[$key] = array_merge($data[$key] ?? [], $value);
}
foreach ($dataMeta['models'] ?? [] as $key => $value) {
$data[$key] = array_merge($data[$key] ?? [], $value);
}
return $data;
}
protected function resolveUpdates()
{
$updates = $this->request->input('updates') ?? [];
return array_map(function (array $update) {
$update['payload'] = Arr::except($update['payload'] ?? [], ['id']);
return $update;
}, $updates);
}
}

View File

@@ -1,36 +0,0 @@
<?php
namespace Facade\Ignition\DumpRecorder;
class Dump
{
/** @var string */
protected $htmlDump;
/** @var ?string */
protected $file;
/** @var ?int */
protected $lineNumber;
/** @var float */
protected $microtime;
public function __construct(string $htmlDump, ?string $file, ?int $lineNumber, ?float $microtime = null)
{
$this->htmlDump = $htmlDump;
$this->file = $file;
$this->lineNumber = $lineNumber;
$this->microtime = $microtime ?? microtime(true);
}
public function toArray(): array
{
return [
'html_dump' => $this->htmlDump,
'file' => $this->file,
'line_number' => $this->lineNumber,
'microtime' => $this->microtime,
];
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace Facade\Ignition\DumpRecorder;
use Symfony\Component\VarDumper\Cloner\VarCloner;
class DumpHandler
{
/** @var \Facade\Ignition\DumpRecorder\DumpRecorder */
protected $dumpRecorder;
public function __construct(DumpRecorder $dumpRecorder)
{
$this->dumpRecorder = $dumpRecorder;
}
public function dump($value)
{
$data = (new VarCloner())->cloneVar($value);
$this->dumpRecorder->record($data);
}
}

View File

@@ -1,112 +0,0 @@
<?php
namespace Facade\Ignition\DumpRecorder;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Arr;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper as BaseHtmlDumper;
use Symfony\Component\VarDumper\VarDumper;
class DumpRecorder
{
protected $dumps = [];
/** @var \Illuminate\Contracts\Foundation\Application */
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function register(): self
{
$multiDumpHandler = new MultiDumpHandler();
$this->app->singleton(MultiDumpHandler::class, function () use ($multiDumpHandler) {
return $multiDumpHandler;
});
$previousHandler = VarDumper::setHandler(function ($var) use ($multiDumpHandler) {
$multiDumpHandler->dump($var);
});
if ($previousHandler) {
$multiDumpHandler->addHandler($previousHandler);
} else {
$multiDumpHandler->addHandler($this->getDefaultHandler());
}
$multiDumpHandler->addHandler(function ($var) {
(new DumpHandler($this))->dump($var);
});
return $this;
}
public function record(Data $data)
{
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 8);
$file = (string)Arr::get($backtrace, '6.file');
$lineNumber = (int)Arr::get($backtrace, '6.line');
if (! Arr::exists($backtrace, '7.class') && (string)Arr::get($backtrace, '7.function') === 'ddd') {
$file = (string)Arr::get($backtrace, '7.file');
$lineNumber = (int)Arr::get($backtrace, '7.line');
}
$htmlDump = (new HtmlDumper())->dump($data);
$this->dumps[] = new Dump($htmlDump, $file, $lineNumber);
}
public function getDumps(): array
{
return $this->toArray();
}
public function reset()
{
$this->dumps = [];
}
public function toArray(): array
{
$dumps = [];
foreach ($this->dumps as $dump) {
$dumps[] = $dump->toArray();
}
return $dumps;
}
protected function getDefaultHandler()
{
return function ($value) {
$data = (new VarCloner())->cloneVar($value);
$this->getDumper()->dump($data);
};
}
protected function getDumper()
{
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
if ($_SERVER['VAR_DUMPER_FORMAT'] === 'html') {
return new BaseHtmlDumper();
}
return new CliDumper();
}
if (in_array(PHP_SAPI, ['cli', 'phpdbg']) && ! isset($_SERVER['LARAVEL_OCTANE'])) {
return new CliDumper() ;
}
return new BaseHtmlDumper();
}
}

View File

@@ -1,29 +0,0 @@
<?php
namespace Facade\Ignition\DumpRecorder;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper as BaseHtmlDumper;
class HtmlDumper extends BaseHtmlDumper
{
protected $dumpHeader = '';
public function dumpVariable($variable): string
{
$cloner = new VarCloner();
$clonedData = $cloner->cloneVar($variable)->withMaxDepth(3);
return $this->dump($clonedData);
}
public function dump(Data $data, $output = null, array $extraDisplayOptions = []): string
{
return (string)parent::dump($data, true, [
'maxDepth' => 3,
'maxStringLength' => 160,
]);
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace Facade\Ignition\DumpRecorder;
class MultiDumpHandler
{
/** @var array */
protected $handlers = [];
public function dump($value)
{
foreach ($this->handlers as $handler) {
$handler($value);
}
}
public function addHandler(callable $callable = null): self
{
$this->handlers[] = $callable;
return $this;
}
}

View File

@@ -1,77 +0,0 @@
<?php
namespace Facade\Ignition\ErrorPage;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Report;
use Facade\Ignition\IgnitionConfig;
use Facade\IgnitionContracts\SolutionProviderRepository;
use Illuminate\Foundation\Application;
use Throwable;
class ErrorPageHandler
{
/** @var \Facade\Ignition\IgnitionConfig */
protected $ignitionConfig;
/** @var \Facade\FlareClient\Flare */
protected $flareClient;
/** @var \Facade\Ignition\ErrorPage\Renderer */
protected $renderer;
/** @var \Facade\IgnitionContracts\SolutionProviderRepository */
protected $solutionProviderRepository;
public function __construct(
Application $app,
IgnitionConfig $ignitionConfig,
Renderer $renderer,
SolutionProviderRepository $solutionProviderRepository
) {
$this->flareClient = $app->make(Flare::class);
$this->ignitionConfig = $ignitionConfig;
$this->renderer = $renderer;
$this->solutionProviderRepository = $solutionProviderRepository;
}
public function handle(Throwable $throwable, $defaultTab = null, $defaultTabProps = [])
{
$report = $this->flareClient->createReport($throwable);
$solutions = $this->solutionProviderRepository->getSolutionsForThrowable($throwable);
$viewModel = new ErrorPageViewModel(
$throwable,
$this->ignitionConfig,
$report,
$solutions
);
$viewModel->defaultTab($defaultTab, $defaultTabProps);
$this->renderException($viewModel);
}
public function handleReport(Report $report, $defaultTab = null, $defaultTabProps = [])
{
$viewModel = new ErrorPageViewModel(
$report->getThrowable(),
$this->ignitionConfig,
$report,
[]
);
$viewModel->defaultTab($defaultTab, $defaultTabProps);
$this->renderException($viewModel);
}
protected function renderException(ErrorPageViewModel $exceptionViewModel)
{
echo $this->renderer->render(
'errorPage',
$exceptionViewModel->toArray()
);
}
}

View File

@@ -1,200 +0,0 @@
<?php
namespace Facade\Ignition\ErrorPage;
use Closure;
use Exception;
use Facade\FlareClient\Report;
use Facade\Ignition\Ignition;
use Facade\Ignition\IgnitionConfig;
use Facade\Ignition\Solutions\SolutionTransformer;
use Illuminate\Contracts\Support\Arrayable;
use Laravel\Telescope\Http\Controllers\HomeController;
use Laravel\Telescope\IncomingExceptionEntry;
use Laravel\Telescope\Telescope;
use Throwable;
class ErrorPageViewModel implements Arrayable
{
/** @var \Throwable|null */
protected $throwable;
/** @var array */
protected $solutions;
/** @var \Facade\Ignition\IgnitionConfig */
protected $ignitionConfig;
/** @var \Facade\FlareClient\Report */
protected $report;
/** @var string */
protected $defaultTab;
/** @var array */
protected $defaultTabProps = [];
/** @var string */
protected $appEnv;
/** @var bool */
protected $appDebug;
public function __construct(?Throwable $throwable, IgnitionConfig $ignitionConfig, Report $report, array $solutions)
{
$this->throwable = $throwable;
$this->ignitionConfig = $ignitionConfig;
$this->report = $report;
$this->solutions = $solutions;
$this->appEnv = config('app.env');
$this->appDebug = config('app.debug');
}
public function throwableString(): string
{
if (! $this->throwable) {
return '';
}
$throwableString = sprintf(
"%s: %s in file %s on line %d\n\n%s\n",
get_class($this->throwable),
$this->throwable->getMessage(),
$this->throwable->getFile(),
$this->throwable->getLine(),
$this->report->getThrowable()->getTraceAsString()
);
return htmlspecialchars($throwableString);
}
public function telescopeUrl(): ?string
{
try {
if (! class_exists(Telescope::class)) {
return null;
}
if (! count(Telescope::$entriesQueue)) {
return null;
}
$telescopeEntry = collect(Telescope::$entriesQueue)->first(function ($entry) {
return $entry instanceof IncomingExceptionEntry;
});
if (is_null($telescopeEntry)) {
return null;
}
$telescopeEntryId = (string) $telescopeEntry->uuid;
return url(action([HomeController::class, 'index'])."/exceptions/{$telescopeEntryId}");
} catch (Exception $exception) {
return null;
}
}
public function title(): string
{
$message = htmlspecialchars($this->report->getMessage());
return "🧨 {$message}";
}
public function config(): array
{
return $this->ignitionConfig->toArray();
}
public function solutions(): array
{
$solutions = [];
foreach ($this->solutions as $solution) {
$solutions[] = (new SolutionTransformer($solution))->toArray();
}
return $solutions;
}
protected function shareEndpoint(): string
{
try {
// use string notation as L5.5 and L5.6 don't support array notation yet
return action('\Facade\Ignition\Http\Controllers\ShareReportController');
} catch (Exception $exception) {
return '';
}
}
public function report(): array
{
return $this->report->toArray();
}
public function jsonEncode($data): string
{
$jsonOptions = JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
return json_encode($data, $jsonOptions);
}
public function getAssetContents(string $asset): string
{
$assetPath = __DIR__."/../../resources/compiled/{$asset}";
return file_get_contents($assetPath);
}
public function styles(): array
{
return array_keys(Ignition::styles());
}
public function scripts(): array
{
return array_keys(Ignition::scripts());
}
public function tabs(): string
{
return json_encode(Ignition::$tabs);
}
public function defaultTab(?string $defaultTab, ?array $defaultTabProps)
{
$this->defaultTab = $defaultTab ?? 'StackTab';
if ($defaultTabProps) {
$this->defaultTabProps = $defaultTabProps;
}
}
public function toArray(): array
{
return [
'throwableString' => $this->throwableString(),
'telescopeUrl' => $this->telescopeUrl(),
'shareEndpoint' => $this->shareEndpoint(),
'title' => $this->title(),
'config' => $this->config(),
'solutions' => $this->solutions(),
'report' => $this->report(),
'housekeepingEndpoint' => url(config('ignition.housekeeping_endpoint_prefix', '_ignition')),
'styles' => $this->styles(),
'scripts' => $this->scripts(),
'tabs' => $this->tabs(),
'jsonEncode' => Closure::fromCallable([$this, 'jsonEncode']),
'getAssetContents' => Closure::fromCallable([$this, 'getAssetContents']),
'defaultTab' => $this->defaultTab,
'defaultTabProps' => $this->defaultTabProps,
'appEnv' => $this->appEnv,
'appDebug' => $this->appDebug,
];
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace Facade\Ignition\ErrorPage;
use Illuminate\Contracts\Foundation\ExceptionRenderer;
/** @psalm-suppress UndefinedClass */
class IgnitionExceptionRenderer implements ExceptionRenderer
{
/** @var \Facade\Ignition\ErrorPage\ErrorPageHandler */
protected $errorPageHandler;
public function __construct(ErrorPageHandler $errorPageHandler)
{
$this->errorPageHandler = $errorPageHandler;
}
public function render($throwable)
{
ob_start();
$this->errorPageHandler->handle($throwable);
return ob_get_clean();
}
}

View File

@@ -1,48 +0,0 @@
<?php
namespace Facade\Ignition\ErrorPage;
use Error;
use ErrorException;
use Whoops\Handler\Handler;
class IgnitionWhoopsHandler extends Handler
{
/** @var \Facade\Ignition\ErrorPage\ErrorPageHandler */
protected $errorPageHandler;
/** @var \Throwable */
protected $exception;
public function __construct(ErrorPageHandler $errorPageHandler)
{
$this->errorPageHandler = $errorPageHandler;
}
public function handle(): ?int
{
try {
$this->errorPageHandler->handle($this->exception);
} catch (Error $error) {
// Errors aren't caught by Whoops.
// Convert the error to an exception and throw again.
throw new ErrorException(
$error->getMessage(),
$error->getCode(),
1,
$error->getFile(),
$error->getLine(),
$error
);
}
return Handler::QUIT;
}
/** @param \Throwable $exception */
public function setException($exception): void
{
$this->exception = $exception;
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace Facade\Ignition\ErrorPage;
use Exception;
use Facade\Ignition\Exceptions\ViewException;
class Renderer
{
/** @var string */
protected $viewPath;
public function __construct(string $viewPath)
{
$this->viewPath = $this->formatPath($viewPath);
}
public function render(string $viewName, array $_data): string
{
ob_start();
$viewFile = "{$this->viewPath}/{$viewName}.php";
try {
extract($_data, EXTR_OVERWRITE);
include $viewFile;
} catch (Exception $exception) {
$viewException = new ViewException($exception->getMessage());
$viewException->setView($viewFile);
$viewException->setViewData($_data);
throw $viewException;
}
return ob_get_clean();
}
protected function formatPath(string $path): string
{
return preg_replace('/(?:\/)+$/u', '', $path).'/';
}
}

View File

@@ -1,29 +0,0 @@
<?php
namespace Facade\Ignition\Exceptions;
use Exception;
use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\ProvidesSolution;
use Facade\IgnitionContracts\Solution;
use Monolog\Logger;
class InvalidConfig extends Exception implements ProvidesSolution
{
public static function invalidLogLevel(string $logLevel)
{
return new static("Invalid log level `{$logLevel}` specified.");
}
public function getSolution(): Solution
{
$validLogLevels = array_map(function (string $level) {
return strtolower($level);
}, array_keys(Logger::getLevels()));
$validLogLevelsString = implode(',', $validLogLevels);
return BaseSolution::create('You provided an invalid log level')
->setSolutionDescription("Please change the log level in your `config/logging.php` file. Valid log levels are {$validLogLevelsString}.");
}
}

View File

@@ -1,9 +0,0 @@
<?php
namespace Facade\Ignition\Exceptions;
use Exception;
class UnableToShareErrorException extends Exception
{
}

View File

@@ -1,51 +0,0 @@
<?php
namespace Facade\Ignition\Exceptions;
use ErrorException;
use Facade\FlareClient\Contracts\ProvidesFlareContext;
use Facade\Ignition\DumpRecorder\HtmlDumper;
class ViewException extends ErrorException implements ProvidesFlareContext
{
/** @var array */
protected $viewData = [];
/** @var string */
protected $view = '';
public function setViewData(array $data)
{
$this->viewData = $data;
}
public function getViewData(): array
{
return $this->viewData;
}
public function setView(string $path)
{
$this->view = $path;
}
protected function dumpViewData($variable): string
{
return (new HtmlDumper())->dumpVariable($variable);
}
public function context(): array
{
$context = [
'view' => [
'view' => $this->view,
],
];
if (config('flare.reporting.report_view_data')) {
$context['view']['data'] = array_map([$this, 'dumpViewData'], $this->viewData);
}
return $context;
}
}

View File

@@ -1,22 +0,0 @@
<?php
namespace Facade\Ignition\Exceptions;
use Facade\IgnitionContracts\ProvidesSolution;
use Facade\IgnitionContracts\Solution;
class ViewExceptionWithSolution extends ViewException implements ProvidesSolution
{
/** @var Solution */
protected $solution;
public function setSolution(Solution $solution)
{
$this->solution = $solution;
}
public function getSolution(): Solution
{
return $this->solution;
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace Facade\Ignition\Facades;
use Facade\Ignition\Support\SentReports;
use Illuminate\Support\Facades\Facade;
/**
* Class Flare.
*
* @method static void glow(string $name, string $messageLevel = \Facade\FlareClient\Enums\MessageLevels::INFO, array $metaData = [])
* @method static void context($key, $value)
* @method static void group(string $groupName, array $properties)
*
* @see \Facade\FlareClient\Flare
*/
class Flare extends Facade
{
protected static function getFacadeAccessor()
{
return \Facade\FlareClient\Flare::class;
}
public static function sentReports(): SentReports
{
return app(SentReports::class);
}
}

View File

@@ -1,46 +0,0 @@
<?php
namespace Facade\Ignition\Http\Controllers;
use Facade\Ignition\Http\Requests\ExecuteSolutionRequest;
use Facade\IgnitionContracts\SolutionProviderRepository;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ExecuteSolutionController
{
use ValidatesRequests;
public function __invoke(
ExecuteSolutionRequest $request,
SolutionProviderRepository $solutionProviderRepository
) {
$this->ensureLocalEnvironment();
$this->ensureLocalRequest();
$solution = $request->getRunnableSolution();
$solution->run($request->get('parameters', []));
return response('');
}
public function ensureLocalEnvironment()
{
if (! app()->environment('local')) {
abort(403, "Runnable solutions are disabled in non-local environments. Please make sure `APP_ENV` is set correctly. Additionally please make sure `APP_DEBUG` is set to false on ANY production environment!");
}
}
public function ensureLocalRequest()
{
$ipIsPublic = filter_var(
request()->ip(),
FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
);
if ($ipIsPublic) {
abort(403, "Solutions can only be executed by requests from a local IP address. Please also make sure `APP_DEBUG` is set to false on ANY production environment.");
}
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Facade\Ignition\Http\Controllers;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Str;
class HealthCheckController
{
public function __invoke()
{
return [
'can_execute_commands' => $this->canExecuteCommands(),
];
}
protected function canExecuteCommands(): bool
{
Artisan::call('help', ['--version']);
$output = Artisan::output();
return Str::contains($output, app()->version());
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace Facade\Ignition\Http\Controllers;
use Facade\Ignition\Ignition;
use Illuminate\Http\Request;
class ScriptController
{
public function __invoke(Request $request)
{
if (! isset(Ignition::scripts()[$request->script])) {
abort(404, 'Script not found');
}
return response(
file_get_contents(
Ignition::scripts()[$request->script]
),
200,
['Content-Type' => 'application/javascript']
);
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace Facade\Ignition\Http\Controllers;
use Facade\Ignition\Actions\ShareReportAction;
use Facade\Ignition\Exceptions\UnableToShareErrorException;
use Facade\Ignition\Http\Requests\ShareReportRequest;
class ShareReportController
{
public function __invoke(ShareReportRequest $request, ShareReportAction $shareReportAction)
{
try {
return $shareReportAction->handle(json_decode($request->get('report'), true), $request->get('tabs'), $request->get('lineSelection'));
} catch (UnableToShareErrorException $exception) {
abort(500, 'Unable to share the error '.$exception->getMessage());
}
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Facade\Ignition\Http\Controllers;
use Facade\Ignition\Ignition;
use Illuminate\Http\Request;
class StyleController
{
public function __invoke(Request $request)
{
return response(
file_get_contents(Ignition::styles()[$request->style]),
200,
['Content-Type' => 'text/css']
);
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace Facade\Ignition\Http\Middleware;
use Closure;
use Facade\Ignition\IgnitionConfig;
use Illuminate\Http\Request;
class IgnitionConfigValueEnabled
{
/** @var \Facade\Ignition\IgnitionConfig */
protected $ignitionConfig;
public function __construct(IgnitionConfig $ignitionConfig)
{
$this->ignitionConfig = $ignitionConfig;
}
public function handle(Request $request, Closure $next, string $value)
{
if (! $this->ignitionConfig->toArray()[$value]) {
abort(404);
}
return $next($request);
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace Facade\Ignition\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class IgnitionEnabled
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (! $this->ignitionEnabled()) {
abort(404);
}
return $next($request);
}
protected function ignitionEnabled(): bool
{
return config('app.debug');
}
}

View File

@@ -1,41 +0,0 @@
<?php
namespace Facade\Ignition\Http\Requests;
use Facade\IgnitionContracts\RunnableSolution;
use Facade\IgnitionContracts\Solution;
use Facade\IgnitionContracts\SolutionProviderRepository;
use Illuminate\Foundation\Http\FormRequest;
class ExecuteSolutionRequest extends FormRequest
{
public function rules(): array
{
return [
'solution' => 'required',
'parameters' => 'array',
];
}
public function getSolution(): Solution
{
$solution = app(SolutionProviderRepository::class)
->getSolutionForClass($this->get('solution'));
abort_if(is_null($solution), 404, 'Solution could not be found');
/** @var Solution */
return $solution;
}
public function getRunnableSolution(): RunnableSolution
{
$solution = $this->getSolution();
if (! $solution instanceof RunnableSolution) {
abort(404, 'Runnable solution could not be found');
}
return $solution;
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace Facade\Ignition\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ShareReportRequest extends FormRequest
{
public function rules(): array
{
return [
'report' => 'required',
'tabs' => 'required|array|min:1',
'lineSelection' => [],
];
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace Facade\Ignition;
use Closure;
use Facade\Ignition\Tabs\Tab;
class Ignition
{
/** @var Closure[] */
public static $callBeforeShowingErrorPage = [];
/** @var array */
public static $tabs = [];
public static function tab(Tab $tab)
{
static::$tabs[] = $tab;
}
public static function styles(): array
{
return collect(static::$tabs)->flatMap(function ($tab) {
return $tab->styles;
})
->unique()
->toArray();
}
public static function scripts(): array
{
return collect(static::$tabs)->flatMap(function ($tab) {
return $tab->scripts;
})
->unique()
->toArray();
}
public static function registerAssets(Closure $callable)
{
static::$callBeforeShowingErrorPage[] = $callable;
}
}

View File

@@ -1,75 +0,0 @@
<?php
namespace Facade\Ignition;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
class IgnitionConfig implements Arrayable
{
/** @var array */
protected $options;
public function __construct(array $options = [])
{
$this->options = $this->mergeWithDefaultConfig($options);
}
public function getEditor(): ?string
{
return Arr::get($this->options, 'editor');
}
public function getRemoteSitesPath(): ?string
{
return Arr::get($this->options, 'remote_sites_path');
}
public function getLocalSitesPath(): ?string
{
return Arr::get($this->options, 'local_sites_path');
}
public function getTheme(): ?string
{
return Arr::get($this->options, 'theme');
}
public function getEnableShareButton(): bool
{
if (! app()->isBooted()) {
return false;
}
return Arr::get($this->options, 'enable_share_button', true);
}
public function getEnableRunnableSolutions(): bool
{
$enabled = Arr::get($this->options, 'enable_runnable_solutions', null);
if ($enabled === null) {
$enabled = config('app.debug');
}
return $enabled ?? false;
}
public function toArray(): array
{
return [
'editor' => $this->getEditor(),
'remoteSitesPath' => $this->getRemoteSitesPath(),
'localSitesPath' => $this->getLocalSitesPath(),
'theme' => $this->getTheme(),
'enableShareButton' => $this->getEnableShareButton(),
'enableRunnableSolutions' => $this->getEnableRunnableSolutions(),
'directorySeparator' => DIRECTORY_SEPARATOR,
];
}
protected function mergeWithDefaultConfig(array $options = []): array
{
return array_merge(config('ignition') ?: include __DIR__.'/../config/ignition.php', $options);
}
}

View File

@@ -1,559 +0,0 @@
<?php
namespace Facade\Ignition;
use Exception;
use Facade\FlareClient\Api;
use Facade\FlareClient\Flare;
use Facade\FlareClient\Http\Client;
use Facade\Ignition\Commands\SolutionMakeCommand;
use Facade\Ignition\Commands\SolutionProviderMakeCommand;
use Facade\Ignition\Commands\TestCommand;
use Facade\Ignition\Context\LaravelContextDetector;
use Facade\Ignition\DumpRecorder\DumpRecorder;
use Facade\Ignition\ErrorPage\IgnitionExceptionRenderer;
use Facade\Ignition\ErrorPage\IgnitionWhoopsHandler;
use Facade\Ignition\ErrorPage\Renderer;
use Facade\Ignition\Exceptions\InvalidConfig;
use Facade\Ignition\Http\Controllers\ExecuteSolutionController;
use Facade\Ignition\Http\Controllers\HealthCheckController;
use Facade\Ignition\Http\Controllers\ScriptController;
use Facade\Ignition\Http\Controllers\ShareReportController;
use Facade\Ignition\Http\Controllers\StyleController;
use Facade\Ignition\Http\Middleware\IgnitionConfigValueEnabled;
use Facade\Ignition\Http\Middleware\IgnitionEnabled;
use Facade\Ignition\JobRecorder\JobRecorder;
use Facade\Ignition\Logger\FlareHandler;
use Facade\Ignition\LogRecorder\LogRecorder;
use Facade\Ignition\Middleware\AddDumps;
use Facade\Ignition\Middleware\AddEnvironmentInformation;
use Facade\Ignition\Middleware\AddExceptionInformation;
use Facade\Ignition\Middleware\AddGitInformation;
use Facade\Ignition\Middleware\AddJobInformation;
use Facade\Ignition\Middleware\AddLogs;
use Facade\Ignition\Middleware\AddQueries;
use Facade\Ignition\Middleware\AddSolutions;
use Facade\Ignition\Middleware\SetNotifierName;
use Facade\Ignition\QueryRecorder\QueryRecorder;
use Facade\Ignition\SolutionProviders\BadMethodCallSolutionProvider;
use Facade\Ignition\SolutionProviders\DefaultDbNameSolutionProvider;
use Facade\Ignition\SolutionProviders\IncorrectValetDbCredentialsSolutionProvider;
use Facade\Ignition\SolutionProviders\InvalidRouteActionSolutionProvider;
use Facade\Ignition\SolutionProviders\LazyLoadingViolationSolutionProvider;
use Facade\Ignition\SolutionProviders\MergeConflictSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingAppKeySolutionProvider;
use Facade\Ignition\SolutionProviders\MissingColumnSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingImportSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingLivewireComponentSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingMixManifestSolutionProvider;
use Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider;
use Facade\Ignition\SolutionProviders\RunningLaravelDuskInProductionProvider;
use Facade\Ignition\SolutionProviders\SolutionProviderRepository;
use Facade\Ignition\SolutionProviders\TableNotFoundSolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedLivewireMethodSolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedLivewirePropertySolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedPropertySolutionProvider;
use Facade\Ignition\SolutionProviders\UndefinedVariableSolutionProvider;
use Facade\Ignition\SolutionProviders\UnknownValidationSolutionProvider;
use Facade\Ignition\SolutionProviders\ViewNotFoundSolutionProvider;
use Facade\Ignition\Support\SentReports;
use Facade\Ignition\Views\Engines\CompilerEngine;
use Facade\Ignition\Views\Engines\PhpEngine;
use Facade\IgnitionContracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
use Illuminate\Foundation\Application;
use Illuminate\Log\Events\MessageLogged;
use Illuminate\Log\LogManager;
use Illuminate\Queue\QueueManager;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Engines\CompilerEngine as LaravelCompilerEngine;
use Illuminate\View\Engines\PhpEngine as LaravelPhpEngine;
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\TaskReceived;
use Laravel\Octane\Events\TickReceived;
use Livewire\CompilerEngineForIgnition;
use Monolog\Logger;
use Throwable;
class IgnitionServiceProvider extends ServiceProvider
{
public function boot()
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/flare.php' => config_path('flare.php'),
], 'flare-config');
$this->publishes([
__DIR__.'/../config/ignition.php' => config_path('ignition.php'),
], 'ignition-config');
if (isset($_SERVER['argv']) && ['artisan', 'tinker'] === $_SERVER['argv']) {
Api::sendReportsInBatches(false);
}
$this->app->make(JobRecorder::class)->register();
}
$this
->registerViewEngines()
->registerHousekeepingRoutes()
->registerLogHandler()
->registerCommands();
if ($this->app->bound('queue')) {
$this->setupQueue($this->app->get('queue'));
}
if (isset($_SERVER['LARAVEL_OCTANE'])) {
$this->setupOctane();
}
if (config('flare.reporting.report_logs', true)) {
$this->app->make(LogRecorder::class)->register();
}
if (config('flare.reporting.report_queries', true)) {
$this->app->make(QueryRecorder::class)->register();
}
$this->app->make(DumpRecorder::class)->register();
}
public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/flare.php', 'flare');
$this->mergeConfigFrom(__DIR__.'/../config/ignition.php', 'ignition');
$this
->registerSolutionProviderRepository()
->registerRenderer()
->registerExceptionRenderer()
->registerIgnitionConfig()
->registerFlare()
->registerDumpCollector()
->registerJobRecorder();
if (config('flare.reporting.report_logs', true)) {
$this->registerLogRecorder();
}
if (config('flare.reporting.report_queries', true)) {
$this->registerQueryRecorder();
}
if (config('flare.reporting.anonymize_ips')) {
$this->app->get(Flare::class)->anonymizeIp();
}
$this->app->get(Flare::class)->censorRequestBodyFields(config('flare.reporting.censor_request_body_fields', ['password']));
$this->registerBuiltInMiddleware();
}
protected function registerViewEngines()
{
if (! $this->hasCustomViewEnginesRegistered()) {
return $this;
}
$this->app->make('view.engine.resolver')->register('php', function () {
return new PhpEngine($this->app['files']);
});
$this->app->make('view.engine.resolver')->register('blade', function () {
if (class_exists(CompilerEngineForIgnition::class)) {
return new CompilerEngineForIgnition($this->app['blade.compiler']);
}
return new CompilerEngine($this->app['blade.compiler']);
});
return $this;
}
protected function registerHousekeepingRoutes()
{
if ($this->app->runningInConsole()) {
return $this;
}
Route::group([
'as' => 'ignition.',
'prefix' => config('ignition.housekeeping_endpoint_prefix', '_ignition'),
'middleware' => [IgnitionEnabled::class],
], function () {
Route::get('health-check', HealthCheckController::class)->name('healthCheck');
Route::post('execute-solution', ExecuteSolutionController::class)
->middleware(IgnitionConfigValueEnabled::class.':enableRunnableSolutions')
->name('executeSolution');
Route::post('share-report', ShareReportController::class)
->middleware(IgnitionConfigValueEnabled::class.':enableShareButton')
->name('shareReport');
Route::get('scripts/{script}', ScriptController::class)->name('scripts');
Route::get('styles/{style}', StyleController::class)->name('styles');
});
return $this;
}
protected function registerSolutionProviderRepository()
{
$this->app->singleton(SolutionProviderRepositoryContract::class, function () {
$defaultSolutions = $this->getDefaultSolutions();
return new SolutionProviderRepository($defaultSolutions);
});
return $this;
}
protected function registerRenderer()
{
$this->app->bind(Renderer::class, function () {
return new Renderer(__DIR__.'/../resources/views/');
});
return $this;
}
protected function registerExceptionRenderer()
{
if (interface_exists(\Whoops\Handler\HandlerInterface::class)) {
$this->app->bind(\Whoops\Handler\HandlerInterface::class, function (Application $app) {
return $app->make(IgnitionWhoopsHandler::class);
});
}
if (interface_exists(\Illuminate\Contracts\Foundation\ExceptionRenderer::class)) {
$this->app->bind(\Illuminate\Contracts\Foundation\ExceptionRenderer::class, function (Application $app) {
return $app->make(IgnitionExceptionRenderer::class);
});
}
return $this;
}
protected function registerIgnitionConfig()
{
$this->app->singleton(IgnitionConfig::class, function () {
$options = [];
try {
if ($configPath = $this->getConfigFileLocation()) {
$options = require $configPath;
}
} catch (Throwable $e) {
// possible open_basedir restriction
}
return new IgnitionConfig($options);
});
return $this;
}
protected function registerFlare()
{
$this->app->singleton('flare.http', function () {
return new Client(
config('flare.key'),
config('flare.secret'),
config('flare.base_url', 'https://reporting.flareapp.io/api')
);
});
$this->app->singleton(SentReports::class);
$this->app->alias('flare.http', Client::class);
$this->app->singleton(Flare::class, function () {
$client = new Flare($this->app->get('flare.http'), new LaravelContextDetector(), $this->app);
$client->applicationPath(base_path());
$client->stage(config('app.env'));
return $client;
});
return $this;
}
protected function registerLogHandler()
{
$this->app->singleton('flare.logger', function ($app) {
$handler = new FlareHandler(
$app->make(Flare::class),
$app->make(SentReports::class)
);
$logLevelString = config('logging.channels.flare.level', 'error');
$logLevel = $this->getLogLevel($logLevelString);
$handler->setMinimumReportLogLevel($logLevel);
$logger = new Logger('Flare');
$logger->pushHandler($handler);
return $logger;
});
if ($this->app['log'] instanceof LogManager) {
Log::extend('flare', function ($app) {
return $app['flare.logger'];
});
} else {
$this->bindLogListener();
}
return $this;
}
protected function getLogLevel(string $logLevelString): int
{
$logLevel = Logger::getLevels()[strtoupper($logLevelString)] ?? null;
if (! $logLevel) {
throw InvalidConfig::invalidLogLevel($logLevelString);
}
return $logLevel;
}
protected function registerLogRecorder(): self
{
$this->app->singleton(LogRecorder::class, function (Application $app): LogRecorder {
return new LogRecorder(
$app,
$app->get('config')->get('flare.reporting.maximum_number_of_collected_logs')
);
});
return $this;
}
protected function registerDumpCollector()
{
$dumpCollector = $this->app->make(DumpRecorder::class);
$this->app->singleton(DumpRecorder::class);
$this->app->instance(DumpRecorder::class, $dumpCollector);
return $this;
}
protected function registerJobRecorder()
{
if (! $this->app->runningInConsole()) {
return $this;
}
$this->app->singleton(JobRecorder::class);
return $this;
}
protected function registerCommands()
{
$this->app->bind('command.flare:test', TestCommand::class);
$this->app->bind('command.make:solution', SolutionMakeCommand::class);
$this->app->bind('command.make:solution-provider', SolutionProviderMakeCommand::class);
if ($this->app['config']->get('flare.key')) {
$this->commands(['command.flare:test']);
}
if ($this->app['config']->get('ignition.register_commands', false)) {
$this->commands(['command.make:solution']);
$this->commands(['command.make:solution-provider']);
}
return $this;
}
protected function registerQueryRecorder(): self
{
$this->app->singleton(QueryRecorder::class, function (Application $app): QueryRecorder {
return new QueryRecorder(
$app,
$app->get('config')->get('flare.reporting.report_query_bindings'),
$app->get('config')->get('flare.reporting.maximum_number_of_collected_queries')
);
});
return $this;
}
protected function registerBuiltInMiddleware()
{
$middlewares = [
SetNotifierName::class,
AddEnvironmentInformation::class,
AddExceptionInformation::class,
];
if (config('flare.reporting.report_logs', true)) {
$middlewares[] = AddLogs::class;
}
$middlewares[] = AddDumps::class;
if (config('flare.reporting.report_queries', true)) {
$middlewares[] = AddQueries::class;
}
$middlewares[] = AddSolutions::class;
if ($this->app->runningInConsole()) {
$middlewares[] = AddJobInformation::class;
}
$middleware = collect($middlewares)
->map(function (string $middlewareClass) {
return $this->app->make($middlewareClass);
});
if (config('flare.reporting.collect_git_information')) {
$middleware[] = (new AddGitInformation());
}
foreach ($middleware as $singleMiddleware) {
$this->app->get(Flare::class)->registerMiddleware($singleMiddleware);
}
return $this;
}
protected function getDefaultSolutions(): array
{
return [
IncorrectValetDbCredentialsSolutionProvider::class,
MissingAppKeySolutionProvider::class,
DefaultDbNameSolutionProvider::class,
BadMethodCallSolutionProvider::class,
TableNotFoundSolutionProvider::class,
MissingImportSolutionProvider::class,
MissingPackageSolutionProvider::class,
InvalidRouteActionSolutionProvider::class,
ViewNotFoundSolutionProvider::class,
UndefinedVariableSolutionProvider::class,
MergeConflictSolutionProvider::class,
RunningLaravelDuskInProductionProvider::class,
MissingColumnSolutionProvider::class,
UnknownValidationSolutionProvider::class,
UndefinedLivewireMethodSolutionProvider::class,
UndefinedLivewirePropertySolutionProvider::class,
UndefinedPropertySolutionProvider::class,
MissingMixManifestSolutionProvider::class,
MissingLivewireComponentSolutionProvider::class,
LazyLoadingViolationSolutionProvider::class,
];
}
protected function hasCustomViewEnginesRegistered()
{
$resolver = $this->app->make('view.engine.resolver');
if (! $resolver->resolve('php') instanceof LaravelPhpEngine) {
return false;
}
if (! $resolver->resolve('blade') instanceof LaravelCompilerEngine) {
return false;
}
return true;
}
protected function bindLogListener()
{
$this->app['log']->listen(function (MessageLogged $messageLogged) {
if (config('flare.key')) {
try {
$this->app['flare.logger']->log(
$messageLogged->level,
$messageLogged->message,
$messageLogged->context
);
} catch (Exception $exception) {
return;
}
}
});
}
protected function getConfigFileLocation(): ?string
{
$configFullPath = base_path().DIRECTORY_SEPARATOR.'.ignition';
if (file_exists($configFullPath)) {
return $configFullPath;
}
$configFullPath = Arr::get($_SERVER, 'HOME', '').DIRECTORY_SEPARATOR.'.ignition';
if (file_exists($configFullPath)) {
return $configFullPath;
}
return null;
}
protected function resetFlare()
{
$this->app->get(SentReports::class)->clear();
$this->app->get(Flare::class)->reset();
if (config('flare.reporting.report_logs', true)) {
$this->app->make(LogRecorder::class)->reset();
}
if (config('flare.reporting.report_queries', true)) {
$this->app->make(QueryRecorder::class)->reset();
}
if ($this->app->runningInConsole()) {
$this->app->make(JobRecorder::class)->reset();
}
$this->app->make(DumpRecorder::class)->reset();
}
protected function setupQueue(QueueManager $queue)
{
// Reset before executing a queue job to make sure the job's log/query/dump recorders are empty.
// When using a sync queue this also reports the queued reports from previous exceptions.
$queue->before(function () {
$this->resetFlare();
});
// Send queued reports (and reset) after executing a queue job.
$queue->after(function () {
$this->resetFlare();
});
// Note: the $queue->looping() event can't be used because it's not triggered on Vapor
}
/** @psalm-suppress UndefinedClass */
protected function setupOctane()
{
$this->app['events']->listen(RequestReceived::class, function () {
$this->resetFlare();
});
$this->app['events']->listen(TaskReceived::class, function () {
$this->resetFlare();
});
$this->app['events']->listen(TickReceived::class, function () {
$this->resetFlare();
});
}
}

View File

@@ -1,167 +0,0 @@
<?php
namespace Facade\Ignition\JobRecorder;
use DateTime;
use Error;
use Exception;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Queue\CallQueuedClosure;
use Illuminate\Queue\Events\JobExceptionOccurred;
use Illuminate\Queue\Jobs\RedisJob;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionProperty;
use RuntimeException;
class JobRecorder
{
/** @var \Illuminate\Contracts\Foundation\Application */
protected $app;
/** @var \Illuminate\Contracts\Queue\Job|null */
protected $job = null;
public function __construct(Application $app)
{
$this->app = $app;
}
public function register(): self
{
$this->app['events']->listen(JobExceptionOccurred::class, [$this, 'record']);
return $this;
}
public function record(JobExceptionOccurred $event): void
{
$this->job = $event->job;
}
public function getJob(): ?Job
{
return $this->job;
}
public function reset(): void
{
$this->job = null;
}
public function toArray(): array
{
if ($this->job === null) {
return [];
}
return array_merge(
$this->getJobProperties(),
[
'name' => $this->job->resolveName(),
'connection' => $this->job->getConnectionName(),
'queue' => $this->job->getQueue(),
]
);
}
protected function getJobProperties(): array
{
$payload = collect($this->resolveJobPayload());
$properties = [];
foreach ($payload as $key => $value) {
if (! in_array($key, ['job', 'data', 'displayName'])) {
$properties[$key] = $value;
}
}
if ($pushedAt = DateTime::createFromFormat('U.u', $payload->get('pushedAt', ''))) {
$properties['pushedAt'] = $pushedAt->format(DATE_ATOM);
}
try {
$properties['data'] = $this->resolveCommandProperties(
$this->resolveObjectFromCommand($payload['data']['command']),
config('ignition.max_chained_job_reporting_depth', 5)
);
} catch (Exception $exception) {
}
return $properties;
}
protected function resolveJobPayload(): array
{
if (! $this->job instanceof RedisJob) {
return $this->job->payload();
}
try {
return json_decode($this->job->getReservedJob(), true, 512, JSON_THROW_ON_ERROR);
} catch (Exception $e) {
return $this->job->payload();
}
}
protected function resolveCommandProperties(object $command, int $maxChainDepth): array
{
$propertiesToIgnore = ['job', 'closure'];
$properties = collect((new ReflectionClass($command))->getProperties())
->reject(function (ReflectionProperty $property) use ($propertiesToIgnore) {
return in_array($property->name, $propertiesToIgnore);
})
->mapWithKeys(function (ReflectionProperty $property) use ($command) {
try {
$property->setAccessible(true);
return [$property->name => $property->getValue($command)];
} catch (Error $error) {
return [$property->name => 'uninitialized'];
}
});
if ($properties->has('chained')) {
$properties['chained'] = $this->resolveJobChain($properties->get('chained'), $maxChainDepth);
}
return $properties->all();
}
protected function resolveJobChain(array $chainedCommands, int $maxDepth): array
{
if ($maxDepth === 0) {
return ['Ignition stopped recording jobs after this point since the max chain depth was reached'];
}
return array_map(
function (string $command) use ($maxDepth) {
$commandObject = $this->resolveObjectFromCommand($command);
return [
'name' => $commandObject instanceof CallQueuedClosure ? $commandObject->displayName() : get_class($commandObject),
'data' => $this->resolveCommandProperties($commandObject, $maxDepth - 1),
];
},
$chainedCommands
);
}
// Taken from Illuminate\Queue\CallQueuedHandler
protected function resolveObjectFromCommand(string $command): object
{
if (Str::startsWith($command, 'O:')) {
return unserialize($command);
}
if ($this->app->bound(Encrypter::class)) {
return unserialize($this->app[Encrypter::class]->decrypt($command));
}
throw new RuntimeException('Unable to extract job payload.');
}
}

Some files were not shown because too many files have changed in this diff Show More