]> BookStack Code Mirror - bookstack/blob - app/Exceptions/Handler.php
Copying: Fixed issue with non-page links to page permalinks
[bookstack] / app / Exceptions / Handler.php
1 <?php
2
3 namespace BookStack\Exceptions;
4
5 use Illuminate\Auth\AuthenticationException;
6 use Illuminate\Database\Eloquent\ModelNotFoundException;
7 use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
8 use Illuminate\Http\Exceptions\PostTooLargeException;
9 use Illuminate\Http\JsonResponse;
10 use Illuminate\Http\Request;
11 use Illuminate\Http\Response;
12 use Illuminate\Validation\ValidationException;
13 use Symfony\Component\ErrorHandler\Error\FatalError;
14 use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
15 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
16 use Throwable;
17
18 class Handler extends ExceptionHandler
19 {
20     /**
21      * A list of the exception types that are not reported.
22      *
23      * @var array<int, class-string<Throwable>>
24      */
25     protected $dontReport = [
26         NotFoundException::class,
27         StoppedAuthenticationException::class,
28     ];
29
30     /**
31      * A list of the inputs that are never flashed to the session on validation exceptions.
32      *
33      * @var array<int, string>
34      */
35     protected $dontFlash = [
36         'current_password',
37         'password',
38         'password_confirmation',
39     ];
40
41     /**
42      * A function to run upon out of memory.
43      * If it returns a response, that will be provided back to the request
44      * upon an out of memory event.
45      *
46      * @var ?callable(): ?Response
47      */
48     protected $onOutOfMemory = null;
49
50     /**
51      * Report or log an exception.
52      *
53      * @param Throwable $exception
54      *
55      * @return void
56      *@throws Throwable
57      *
58      */
59     public function report(Throwable $exception)
60     {
61         parent::report($exception);
62     }
63
64     /**
65      * Render an exception into an HTTP response.
66      *
67      * @param Request $request
68      */
69     public function render($request, Throwable $e): SymfonyResponse
70     {
71         if ($e instanceof FatalError && str_contains($e->getMessage(), 'bytes exhausted (tried to allocate') && $this->onOutOfMemory) {
72             $response = call_user_func($this->onOutOfMemory);
73             if ($response) {
74                 return $response;
75             }
76         }
77
78         if ($e instanceof PostTooLargeException) {
79             $e = new NotifyException(trans('errors.server_post_limit'), '/', 413);
80         }
81
82         if ($this->isApiRequest($request)) {
83             return $this->renderApiException($e);
84         }
85
86         return parent::render($request, $e);
87     }
88
89     /**
90      * Provide a function to be called when an out of memory event occurs.
91      * If the callable returns a response, this response will be returned
92      * to the request upon error.
93      */
94     public function prepareForOutOfMemory(callable $onOutOfMemory): void
95     {
96         $this->onOutOfMemory = $onOutOfMemory;
97     }
98
99     /**
100      * Forget the current out of memory handler, if existing.
101      */
102     public function forgetOutOfMemoryHandler(): void
103     {
104         $this->onOutOfMemory = null;
105     }
106
107     /**
108      * Check if the given request is an API request.
109      */
110     protected function isApiRequest(Request $request): bool
111     {
112         return str_starts_with($request->path(), 'api/');
113     }
114
115     /**
116      * Render an exception when the API is in use.
117      */
118     protected function renderApiException(Throwable $e): JsonResponse
119     {
120         $code = 500;
121         $headers = [];
122
123         if ($e instanceof HttpExceptionInterface) {
124             $code = $e->getStatusCode();
125             $headers = $e->getHeaders();
126         }
127
128         if ($e instanceof ModelNotFoundException) {
129             $code = 404;
130         }
131
132         $responseData = [
133             'error' => [
134                 'message' => $e->getMessage(),
135             ],
136         ];
137
138         if ($e instanceof ValidationException) {
139             $responseData['error']['message'] = 'The given data was invalid.';
140             $responseData['error']['validation'] = $e->errors();
141             $code = $e->status;
142         }
143
144         $responseData['error']['code'] = $code;
145
146         return new JsonResponse($responseData, $code, $headers);
147     }
148
149     /**
150      * Convert an authentication exception into an unauthenticated response.
151      *
152      * @param Request $request
153      */
154     protected function unauthenticated($request, AuthenticationException $exception): SymfonyResponse
155     {
156         if ($request->expectsJson()) {
157             return response()->json(['error' => 'Unauthenticated.'], 401);
158         }
159
160         return redirect()->guest('login');
161     }
162
163     /**
164      * Convert a validation exception into a JSON response.
165      *
166      * @param Request $request
167      */
168     protected function invalidJson($request, ValidationException $exception): JsonResponse
169     {
170         return response()->json($exception->errors(), $exception->status);
171     }
172 }