]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/Controller.php
Added streamed uploads for attachments
[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="' . $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             ob_end_clean();
131             fpassthru($stream);
132             fclose($stream);
133         }, 200, [
134             'Content-Type'           => 'application/octet-stream',
135             'Content-Disposition'    => 'attachment; filename="' . $fileName . '"',
136             'X-Content-Type-Options' => 'nosniff',
137         ]);
138     }
139
140     /**
141      * Create a file download response that provides the file with a content-type
142      * correct for the file, in a way so the browser can show the content in browser.
143      */
144     protected function inlineDownloadResponse(string $content, string $fileName): Response
145     {
146         $mime = (new WebSafeMimeSniffer())->sniff($content);
147
148         return response()->make($content, 200, [
149             'Content-Type'           => $mime,
150             'Content-Disposition'    => 'inline; filename="' . $fileName . '"',
151             'X-Content-Type-Options' => 'nosniff',
152         ]);
153     }
154
155     /**
156      * Create a file download response that provides the file with a content-type
157      * correct for the file, in a way so the browser can show the content in browser,
158      * for a given content stream.
159      */
160     protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
161     {
162         $sniffContent = fread($stream, 1000);
163         $mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
164
165         return response()->stream(function() use ($sniffContent, $stream) {
166            echo $sniffContent;
167            fpassthru($stream);
168            fclose($stream);
169         }, 200, [
170             'Content-Type'           => $mime,
171             'Content-Disposition'    => 'inline; filename="' . $fileName . '"',
172             'X-Content-Type-Options' => 'nosniff',
173         ]);
174     }
175
176     /**
177      * Show a positive, successful notification to the user on next view load.
178      */
179     protected function showSuccessNotification(string $message): void
180     {
181         session()->flash('success', $message);
182     }
183
184     /**
185      * Show a warning notification to the user on next view load.
186      */
187     protected function showWarningNotification(string $message): void
188     {
189         session()->flash('warning', $message);
190     }
191
192     /**
193      * Show an error notification to the user on next view load.
194      */
195     protected function showErrorNotification(string $message): void
196     {
197         session()->flash('error', $message);
198     }
199
200     /**
201      * Log an activity in the system.
202      *
203      * @param string|Loggable $detail
204      */
205     protected function logActivity(string $type, $detail = ''): void
206     {
207         Activity::add($type, $detail);
208     }
209
210     /**
211      * Get the validation rules for image files.
212      */
213     protected function getImageValidationRules(): array
214     {
215         return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
216     }
217 }