Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
44 / 44 |
Attachment | |
100.00% |
1 / 1 |
|
100.00% |
5 / 5 |
17 | |
100.00% |
44 / 44 |
attachment | |
100.00% |
1 / 1 |
6 | |
100.00% |
8 / 8 |
|||
attachment_show | |
100.00% |
1 / 1 |
2 | |
100.00% |
9 / 9 |
|||
attachment_view | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
attachment_download | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
attachment_thumbnail | |
100.00% |
1 / 1 |
7 | |
100.00% |
17 / 17 |
1 | <?php |
2 | |
3 | // {{{ License |
4 | |
5 | // This file is part of GNU social - https://www.gnu.org/software/social |
6 | // |
7 | // GNU social is free software: you can redistribute it and/or modify |
8 | // it under the terms of the GNU Affero General Public License as published by |
9 | // the Free Software Foundation, either version 3 of the License, or |
10 | // (at your option) any later version. |
11 | // |
12 | // GNU social is distributed in the hope that it will be useful, |
13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | // GNU Affero General Public License for more details. |
16 | // |
17 | // You should have received a copy of the GNU Affero General Public License |
18 | // along with GNU social. If not, see <http://www.gnu.org/licenses/>. |
19 | |
20 | // }}} |
21 | |
22 | namespace App\Controller; |
23 | |
24 | use App\Core\Controller; |
25 | use App\Core\DB\DB; |
26 | use App\Core\Event; |
27 | use App\Core\GSFile; |
28 | use function App\Core\I18n\_m; |
29 | use App\Core\Log; |
30 | use App\Core\Router\Router; |
31 | use App\Entity\AttachmentThumbnail; |
32 | use App\Util\Common; |
33 | use App\Util\Exception\ClientException; |
34 | use App\Util\Exception\NoSuchFileException; |
35 | use App\Util\Exception\NotFoundException; |
36 | use App\Util\Exception\ServerException; |
37 | use Symfony\Component\HttpFoundation\HeaderUtils; |
38 | use Symfony\Component\HttpFoundation\Request; |
39 | use Symfony\Component\HttpFoundation\Response; |
40 | use Symfony\Component\Mime\MimeTypes; |
41 | |
42 | class Attachment extends Controller |
43 | { |
44 | /** |
45 | * Generic function that handles getting a representation for an attachment |
46 | */ |
47 | private function attachment(int $id, callable $handle) |
48 | { |
49 | if ($id <= 0) { // This should never happen coming from the router, but let's bail if it does |
50 | // @codeCoverageIgnoreStart |
51 | Log::critical("Attachment controller called with {$id}, which should not be possible"); |
52 | throw new ClientException(_m('No such attachment'), 404); |
53 | // @codeCoverageIgnoreEnd |
54 | } else { |
55 | if (Event::handle('AttachmentFileInfo', [$id, &$res]) != Event::stop) { |
56 | // If no one else claims this attachment, use the default representation |
57 | try { |
58 | $res = GSFile::getAttachmentFileInfo($id); |
59 | } catch (NoSuchFileException $e) { |
60 | // Continue below |
61 | } |
62 | } |
63 | } |
64 | |
65 | if (empty($res)) { |
66 | throw new ClientException(_m('No such attachment'), 404); |
67 | } else { |
68 | if (!array_key_exists('filepath', $res)) { |
69 | // @codeCoverageIgnoreStart |
70 | throw new ServerException('This attachment is not stored locally.'); |
71 | // @codeCoverageIgnoreEnd |
72 | } else { |
73 | return $handle($res); |
74 | } |
75 | } |
76 | } |
77 | |
78 | /** |
79 | * The page where the attachment and it's info is shown |
80 | */ |
81 | public function attachment_show(Request $request, int $id) |
82 | { |
83 | try { |
84 | $attachment = DB::findOneBy('attachment', ['id' => $id]); |
85 | return $this->attachment($id, function ($res) use ($id, $attachment) { |
86 | return [ |
87 | '_template' => 'attachments/show.html.twig', |
88 | 'download' => Router::url('attachment_download', ['id' => $id]), |
89 | 'attachment' => $attachment, |
90 | 'right_panel_vars' => ['attachment_id' => $id], |
91 | ]; |
92 | }); |
93 | } catch (NotFoundException) { |
94 | throw new ClientException(_m('No such attachment'), 404); |
95 | } |
96 | } |
97 | |
98 | /** |
99 | * Display the attachment inline |
100 | */ |
101 | public function attachment_view(Request $request, int $id) |
102 | { |
103 | return $this->attachment($id, fn (array $res) => GSFile::sendFile( |
104 | $res['filepath'], $res['mimetype'], |
105 | GSFile::ensureFilenameWithProperExtension($res['filename'], $res['mimetype']) ?? $res['filename'], |
106 | HeaderUtils::DISPOSITION_INLINE |
107 | ) |
108 | ); |
109 | } |
110 | |
111 | public function attachment_download(Request $request, int $id) |
112 | { |
113 | return $this->attachment($id, fn (array $res) => GSFile::sendFile( |
114 | $res['filepath'], $res['mimetype'], |
115 | GSFile::ensureFilenameWithProperExtension($res['filename'], $res['mimetype']) ?? $res['filename'], |
116 | HeaderUtils::DISPOSITION_ATTACHMENT |
117 | ) |
118 | ); |
119 | } |
120 | |
121 | /** |
122 | * Controller to produce a thumbnail for a given attachment id |
123 | * |
124 | * @param Request $request |
125 | * @param int $id Attachment ID |
126 | * |
127 | * @throws ClientException |
128 | * @throws NotFoundException |
129 | * @throws ServerException |
130 | * @throws \App\Util\Exception\DuplicateFoundException |
131 | * |
132 | * @return Response |
133 | */ |
134 | public function attachment_thumbnail(Request $request, int $id): Response |
135 | { |
136 | $attachment = DB::findOneBy('attachment', ['id' => $id]); |
137 | |
138 | $default_width = Common::config('thumbnail', 'width'); |
139 | $default_height = Common::config('thumbnail', 'height'); |
140 | $default_crop = Common::config('thumbnail', 'smart_crop'); |
141 | $width = $this->int('w') ?: $default_width; |
142 | $height = $this->int('h') ?: $default_height; |
143 | $crop = $this->bool('c') ?: $default_crop; |
144 | |
145 | Event::handle('GetAllowedThumbnailSizes', [&$sizes]); |
146 | if (!in_array(['width' => $width, 'height' => $height], $sizes)) { |
147 | throw new ClientException(_m('The requested thumbnail dimensions are not allowed'), 400); // 400 Bad Request |
148 | } |
149 | |
150 | if (!is_null($attachment->getWidth()) && !is_null($attachment->getHeight())) { |
151 | [$width, $height] = AttachmentThumbnail::predictScalingValues($attachment->getWidth(), $attachment->getHeight(), $width, $height, $crop); |
152 | } |
153 | |
154 | $thumbnail = AttachmentThumbnail::getOrCreate(attachment: $attachment, width: $width, height: $height, crop: $crop); |
155 | |
156 | $filename = $thumbnail->getFilename(); |
157 | $path = $thumbnail->getPath(); |
158 | $mimetype = $thumbnail->getMimetype(); |
159 | |
160 | return GSFile::sendFile(filepath: $path, mimetype: $mimetype, output_filename: $filename . '.' . MimeTypes::getDefault()->getExtensions($mimetype)[0], disposition: HeaderUtils::DISPOSITION_INLINE); |
161 | } |
162 | } |