]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/Controller.php
Updated composer deps, applied latest StyleCI changes
[bookstack] / app / Http / Controllers / Controller.php
1 <?php
2
3 namespace BookStack\Http\Controllers;
4
5 use BookStack\Exceptions\NotifyException;
6 use BookStack\Facades\Activity;
7 use BookStack\Interfaces\Loggable;
8 use BookStack\Model;
9 use BookStack\Util\WebSafeMimeSniffer;
10 use Illuminate\Foundation\Bus\DispatchesJobs;
11 use Illuminate\Foundation\Validation\ValidatesRequests;
12 use Illuminate\Http\JsonResponse;
13 use Illuminate\Http\Response;
14 use Illuminate\Routing\Controller as BaseController;
15 use Symfony\Component\HttpFoundation\StreamedResponse;
16
17 abstract class Controller extends BaseController
18 {
19     use DispatchesJobs;
20     use ValidatesRequests;
21
22     /**
23      * Check if the current user is signed in.
24      */
25     protected function isSignedIn(): bool
26     {
27         return auth()->check();
28     }
29
30     /**
31      * Stops the application and shows a permission error if
32      * the application is in demo mode.
33      */
34     protected function preventAccessInDemoMode()
35     {
36         if (config('app.env') === 'demo') {
37             $this->showPermissionError();
38         }
39     }
40
41     /**
42      * Adds the page title into the view.
43      */
44     public function setPageTitle(string $title)
45     {
46         view()->share('pageTitle', $title);
47     }
48
49     /**
50      * On a permission error redirect to home and display.
51      * the error as a notification.
52      *
53      * @return never
54      */
55     protected function showPermissionError()
56     {
57         $message = request()->wantsJson() ? trans('errors.permissionJson') : trans('errors.permission');
58
59         throw new NotifyException($message, '/', 403);
60     }
61
62     /**
63      * Checks that the current user has the given permission otherwise throw an exception.
64      */
65     protected function checkPermission(string $permission): void
66     {
67         if (!user() || !user()->can($permission)) {
68             $this->showPermissionError();
69         }
70     }
71
72     /**
73      * Check the current user's permissions against an ownable item otherwise throw an exception.
74      */
75     protected function checkOwnablePermission(string $permission, Model $ownable): void
76     {
77         if (!userCan($permission, $ownable)) {
78             $this->showPermissionError();
79         }
80     }
81
82     /**
83      * Check if a user has a permission or bypass the permission
84      * check if the given callback resolves true.
85      */
86     protected function checkPermissionOr(string $permission, callable $callback): void
87     {
88         if ($callback() !== true) {
89             $this->checkPermission($permission);
90         }
91     }
92
93     /**
94      * Check if the current user has a permission or bypass if the provided user
95      * id matches the current user.
96      */
97     protected function checkPermissionOrCurrentUser(string $permission, int $userId): void
98     {
99         $this->checkPermissionOr($permission, function () use ($userId) {
100             return $userId === user()->id;
101         });
102     }
103
104     /**
105      * Send back a json error message.
106      */
107     protected function jsonError(string $messageText = '', int $statusCode = 500): JsonResponse
108     {
109         return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode);
110     }
111
112     /**
113      * Create a response that forces a download in the browser.
114      */
115     protected function downloadResponse(string $content, string $fileName): Response
116     {
117         return response()->make($content, 200, [
118             'Content-Type'           => 'application/octet-stream',
119             'Content-Disposition'    => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
120             'X-Content-Type-Options' => 'nosniff',
121         ]);
122     }
123
124     /**
125      * Create a response that forces a download, from a given stream of content.
126      */
127     protected function streamedDownloadResponse($stream, string $fileName): StreamedResponse
128     {
129         return response()->stream(function () use ($stream) {
130             // End & flush the output buffer otherwise we still seem to use memory.
131             // Ignore in testing since output buffers are used to gather a response.
132             if (!app()->runningUnitTests()) {
133                 ob_end_clean();
134             }
135
136             fpassthru($stream);
137             fclose($stream);
138         }, 200, [
139             'Content-Type'           => 'application/octet-stream',
140             'Content-Disposition'    => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
141             'X-Content-Type-Options' => 'nosniff',
142         ]);
143     }
144
145     /**
146      * Create a file download response that provides the file with a content-type
147      * correct for the file, in a way so the browser can show the content in browser.
148      */
149     protected function inlineDownloadResponse(string $content, string $fileName): Response
150     {
151         $mime = (new WebSafeMimeSniffer())->sniff($content);
152
153         return response()->make($content, 200, [
154             'Content-Type'           => $mime,
155             'Content-Disposition'    => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
156             'X-Content-Type-Options' => 'nosniff',
157         ]);
158     }
159
160     /**
161      * Create a file download response that provides the file with a content-type
162      * correct for the file, in a way so the browser can show the content in browser,
163      * for a given content stream.
164      */
165     protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
166     {
167         $sniffContent = fread($stream, 1000);
168         $mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
169
170         return response()->stream(function () use ($sniffContent, $stream) {
171             echo $sniffContent;
172             fpassthru($stream);
173             fclose($stream);
174         }, 200, [
175             'Content-Type'           => $mime,
176             'Content-Disposition'    => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
177             'X-Content-Type-Options' => 'nosniff',
178         ]);
179     }
180
181     /**
182      * Show a positive, successful notification to the user on next view load.
183      */
184     protected function showSuccessNotification(string $message): void
185     {
186         session()->flash('success', $message);
187     }
188
189     /**
190      * Show a warning notification to the user on next view load.
191      */
192     protected function showWarningNotification(string $message): void
193     {
194         session()->flash('warning', $message);
195     }
196
197     /**
198      * Show an error notification to the user on next view load.
199      */
200     protected function showErrorNotification(string $message): void
201     {
202         session()->flash('error', $message);
203     }
204
205     /**
206      * Log an activity in the system.
207      *
208      * @param string|Loggable $detail
209      */
210     protected function logActivity(string $type, $detail = ''): void
211     {
212         Activity::add($type, $detail);
213     }
214
215     /**
216      * Get the validation rules for image files.
217      */
218     protected function getImageValidationRules(): array
219     {
220         return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
221     }
222 }