<?php namespace Common\Core\Exceptions; use Common\Billing\Models\Product; use ErrorException; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Foundation\Exceptions\Handler; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Sentry\Laravel\Integration; use Sentry\State\Scope; use Spatie\Ignition\Ignition; use Symfony\Component\HttpKernel\Exception\HttpException; use Throwable; use function Sentry\configureScope; class BaseExceptionHandler extends Handler { public function render($request, Throwable $e) { $isAuthException = $e instanceof AuthorizationException || ($e instanceof HttpException && $e->getStatusCode() === 403); if ( $isAuthException && (requestIsFromFrontend() && !$request->expectsJson() && !Auth::check()) ) { return redirect('/login'); } if ( $e instanceof AuthorizationException && $e->response() instanceof AccessResponseWithPermission && $e->response()->permission && Auth::check() && settings('billing.enable') ) { $permissionExistsInSubscriptionPlan = Product::with(['permissions']) ->get() ->some(function ($product) use ($e) { // check if there's a plan that has this permission and if user is not already on this plan return $product->permissions->contains( 'name', $e->response()->permission, ) && Auth::user()->subscriptions->first()?->product_id !== $product->id; }); if ($permissionExistsInSubscriptionPlan) { return Auth::user()->subscribed() ? redirect('/billing') : redirect('/pricing'); } } return parent::render($request, $e); } public function register() { if (config('app.env') !== 'production') { return; } $this->renderable(function (ErrorException $e) { if ( Str::contains($e->getMessage(), [ 'failed to open stream: Permission denied', 'mkdir(): Permission denied', ]) ) { return $this->filePermissionResponse($e); } }); configureScope(function (Scope $scope): void { $scope->setContext('app_name', ['value' => config('app.name')]); }); $this->reportable(function (Throwable $e) { Integration::captureUnhandledException($e); }); } protected function convertExceptionToArray(Throwable $e): array { $previous = $e->getPrevious(); $isValidationException = $e instanceof HttpException && $e->getStatusCode() === 422; $isExceptionWithAction = $previous && method_exists($previous, 'response') && $previous->response() && property_exists($previous->response(), 'action'); if ( config('app.debug') && !config('common.site.demo') && !$isValidationException && false ) { $array = $this->ignitionReportFromThrowable($e); } else { $array = parent::convertExceptionToArray($e); } if ($isExceptionWithAction) { $array['action'] = $e->getPrevious()->response()->action; } if ($array['message'] === 'Server Error') { $array['message'] = __( 'There was an issue. Please try again later.', ); } if ($array['message'] === 'This action is unauthorized.') { $array['message'] = __( "You don't have required permissions for this action.", ); } return $array; } protected function filePermissionResponse(ErrorException $e) { if (request()->expectsJson()) { return response()->json(['message' => 'test']); } else { preg_match('/\((.+?)\):/', $e->getMessage(), $matches); $path = $matches[1] ?? null; // should not return a view here, in case laravel views folder is not readable as well return response( "<div style='text-align:center'><h1>Could not access a file or folder</h1> <br> Location: <b>$path</b><br>" . '<p>See the article here for possible solutions: <a target="_blank" href="https://support.vebto.com/hc/articles/21/25/207/changing-file-permissions">https://support.vebto.com/hc/articles/207/changing-file-permissions</a></p></div>', ); } } protected function ignitionReportFromThrowable(Throwable $e): array { $report = app(Ignition::class) ->shouldDisplayException(false) ->handleException($e) ->toArray(); $trace = array_map(function ($item) { $path = Str::of($item['class'] ?? $item['file']) ->replace([base_path(), 'vendor/laravel/framework/src/'], '') ->replace('\\', '/') ->trim('/') ->explode('/'); return [ 'applicationFrame' => $item['application_frame'], 'codeSnippet' => $item['code_snippet'], 'path' => $path, 'lineNumber' => $item['line_number'], 'method' => $item['method'], ]; }, $report['stacktrace']); $flatIndex = 0; $totalVendorGroups = 0; $groupedTrace = array_reduce( $trace, function ($carry, $item) use (&$flatIndex, &$totalVendorGroups) { $item['flatIndex'] = $flatIndex; if ($item['applicationFrame']) { $carry[] = $item; } else { if (Arr::get(Arr::last($carry), 'vendorGroup')) { $carry[count($carry) - 1]['items'][] = $item; } else { $totalVendorGroups++; $carry[] = [ 'vendorGroup' => true, 'items' => [$item], ]; } } $flatIndex++; return $carry; }, [], ); return [ 'ignitionTrace' => true, 'message' => $report['message'], 'exception' => $report['exception_class'], 'file' => $report['stacktrace'][0]['file'], 'line' => $report['stacktrace'][0]['line_number'], 'trace' => $groupedTrace, 'totalVendorGroups' => $totalVendorGroups, 'phpVersion' => $report['language_version'], 'appVersion' => config('common.site.version'), ]; } }