1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * Handles the job of attachment directory management. |
||||
5 | * |
||||
6 | * @package ElkArte Forum |
||||
7 | * @copyright ElkArte Forum contributors |
||||
8 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||||
9 | * |
||||
10 | * @version 2.0 dev |
||||
11 | * |
||||
12 | */ |
||||
13 | |||||
14 | namespace ElkArte\Attachments; |
||||
15 | |||||
16 | /** |
||||
17 | * Class AttachmentsDisplay |
||||
18 | */ |
||||
19 | class AttachmentsDisplay |
||||
20 | { |
||||
21 | /** @var array The good old attachments array */ |
||||
22 | protected $attachments = []; |
||||
23 | |||||
24 | /** @var array The message array */ |
||||
25 | protected $messages = []; |
||||
26 | |||||
27 | /** @var bool If unapproved posts/attachments should be shown */ |
||||
28 | protected $includeUnapproved = false; |
||||
29 | |||||
30 | /** |
||||
31 | * @param int[] $messages |
||||
32 | * @param int[] $posters |
||||
33 | * @param bool $includeUnapproved |
||||
34 | */ |
||||
35 | public function __construct($messages, $posters, $includeUnapproved) |
||||
36 | { |
||||
37 | $this->messages = $messages; |
||||
38 | $this->includeUnapproved = $includeUnapproved; |
||||
39 | |||||
40 | // Fetch attachments. |
||||
41 | if (allowedTo('view_attachments')) |
||||
42 | { |
||||
43 | // Reminder: this should not be necessary, it removes the current user from the list of posters if not present among the actual list of posters |
||||
44 | if (isset($posters[-1])) |
||||
45 | { |
||||
46 | unset($posters[-1]); |
||||
47 | } |
||||
48 | |||||
49 | // The filter returns false when: |
||||
50 | // - the attachment is unapproved, and |
||||
51 | // - the viewer is not the poster of the message where the attachment is |
||||
52 | $this->getAttachments( |
||||
53 | $this->messages, |
||||
54 | $this->includeUnapproved, |
||||
55 | static fn($attachment_info, $all_posters) => !(!$attachment_info['approved'] && (!isset($all_posters[$attachment_info['id_msg']]) || $all_posters[$attachment_info['id_msg']] !== \ElkArte\User::$info->id)), |
||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() function(...) { /* ... */ } of type callable is incompatible with the type null|string expected by parameter $filter of ElkArte\Attachments\Atta...splay::getAttachments() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
56 | $posters |
||||
57 | ); |
||||
58 | } |
||||
59 | } |
||||
60 | |||||
61 | /** |
||||
62 | * Get all attachments associated with a set of posts. |
||||
63 | * |
||||
64 | * What it does: |
||||
65 | * - This does not check permissions. |
||||
66 | * |
||||
67 | * @param int[] $messages array of messages ids |
||||
68 | * @param bool $includeUnapproved = false |
||||
69 | * @param string|null $filter name of a callback function |
||||
70 | * @param array $all_posters |
||||
71 | * |
||||
72 | */ |
||||
73 | protected function getAttachments($messages, $includeUnapproved = false, $filter = null, $all_posters = array()) |
||||
74 | { |
||||
75 | global $modSettings; |
||||
76 | |||||
77 | $db = database(); |
||||
78 | |||||
79 | $attachments = array(); |
||||
80 | $temp = array(); |
||||
81 | $db->fetchQuery(' |
||||
82 | SELECT |
||||
83 | a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved, |
||||
84 | a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ', |
||||
85 | COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . ' |
||||
86 | FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ' |
||||
87 | LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . ' |
||||
88 | WHERE a.id_msg IN ({array_int:message_list}) |
||||
89 | AND a.attachment_type = {int:attachment_type}', |
||||
90 | array( |
||||
91 | 'message_list' => $messages, |
||||
92 | 'attachment_type' => 0, |
||||
93 | ) |
||||
94 | )->fetch_callback( |
||||
95 | static function ($row) use ($includeUnapproved, $filter, $all_posters, &$attachments, &$temp) { |
||||
96 | if (!$row['approved'] && !$includeUnapproved |
||||
97 | && (empty($filter) || !call_user_func($filter, $row, $all_posters))) |
||||
98 | { |
||||
99 | return; |
||||
100 | } |
||||
101 | $temp[$row['id_attach']] = $row; |
||||
102 | if (!isset($attachments[$row['id_msg']])) |
||||
103 | { |
||||
104 | $attachments[$row['id_msg']] = array(); |
||||
105 | } |
||||
106 | } |
||||
107 | ); |
||||
108 | |||||
109 | // This is better than sorting it with the query... |
||||
110 | ksort($temp); |
||||
111 | |||||
112 | foreach ($temp as $row) |
||||
113 | { |
||||
114 | $attachments[$row['id_msg']][] = $row; |
||||
115 | } |
||||
116 | |||||
117 | $this->attachments = $attachments; |
||||
118 | } |
||||
119 | |||||
120 | /** |
||||
121 | * This loads an attachment's contextual data including, most importantly, its size if it is an image. |
||||
122 | * |
||||
123 | * What it does: |
||||
124 | * |
||||
125 | * - Pre-condition: $attachments array to have been filled with the proper attachment data, as Display() does. |
||||
126 | * - It requires the view_attachments permission to calculate image size. |
||||
127 | * - It attempts to keep the "aspect ratio" of the posted image in line, even if it has to be resized by |
||||
128 | * the max_image_width and max_image_height settings. |
||||
129 | * |
||||
130 | * @param int $id_msg message number to load attachments for |
||||
131 | * @return array of attachments |
||||
132 | * @todo change this pre-condition, too fragile and error-prone. |
||||
133 | * |
||||
134 | */ |
||||
135 | public function loadAttachmentContext($id_msg) |
||||
136 | { |
||||
137 | global $context, $modSettings, $scripturl, $topic; |
||||
138 | |||||
139 | // Set up the attachment info - based on code by Meriadoc. |
||||
140 | $attachmentData = []; |
||||
141 | $ilaData = []; |
||||
142 | $have_unapproved = false; |
||||
143 | |||||
144 | if (isset($this->attachments[$id_msg]) && !empty($modSettings['attachmentEnable'])) |
||||
145 | { |
||||
146 | foreach ($this->attachments[$id_msg] as $i => $attachment) |
||||
147 | { |
||||
148 | $attachmentData[$i] = array( |
||||
149 | 'id' => $attachment['id_attach'], |
||||
150 | 'name' => preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8')), |
||||
151 | 'downloads' => $attachment['downloads'], |
||||
152 | 'size' => byte_format($attachment['filesize']), |
||||
153 | 'byte_size' => $attachment['filesize'], |
||||
154 | 'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'], |
||||
155 | 'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'] . '">' . htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8') . '</a>', |
||||
156 | 'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']), |
||||
157 | 'is_approved' => $attachment['approved'], |
||||
158 | 'file_hash' => $attachment['file_hash'], |
||||
159 | ); |
||||
160 | |||||
161 | // If something is unapproved we'll note it so we can sort them. |
||||
162 | if (!$attachment['approved']) |
||||
163 | { |
||||
164 | $have_unapproved = true; |
||||
165 | } |
||||
166 | |||||
167 | if ($attachmentData[$i]['is_image']) |
||||
168 | { |
||||
169 | $this->prepareAttachmentImage($attachmentData[$i], $attachment, $id_msg); |
||||
170 | } |
||||
171 | |||||
172 | // If this is an ILA |
||||
173 | if ($attachment['approved'] |
||||
174 | && !empty($context['ila_dont_show_attach_below']) |
||||
175 | && in_array($attachment['id_attach'], $context['ila_dont_show_attach_below'])) |
||||
176 | { |
||||
177 | $ilaData[$i] = $attachmentData[$i]; |
||||
178 | unset($attachmentData[$i]); |
||||
179 | } |
||||
180 | } |
||||
181 | } |
||||
182 | |||||
183 | // Do we need to instigate a sort? |
||||
184 | if ($have_unapproved) |
||||
185 | { |
||||
186 | // Unapproved attachments go first. |
||||
187 | usort($attachmentData, static fn($a, $b) => $b['is_approved'] <=> $a['is_approved']); |
||||
188 | } |
||||
189 | |||||
190 | return [$attachmentData, $ilaData]; |
||||
191 | } |
||||
192 | |||||
193 | /** |
||||
194 | * Function that prepares image attachments |
||||
195 | * |
||||
196 | * What it does: |
||||
197 | * - Generates thumbnail if non exists, and they are enabled |
||||
198 | * |
||||
199 | * @param $attachmentData |
||||
200 | * @param $attachment |
||||
201 | * @param $id_msg |
||||
202 | * @return void |
||||
203 | */ |
||||
204 | public function prepareAttachmentImage(&$attachmentData, $attachment, $id_msg) |
||||
205 | { |
||||
206 | global $modSettings, $topic; |
||||
207 | |||||
208 | $attachmentData['real_width'] = $attachment['width']; |
||||
209 | $attachmentData['width'] = $attachment['width']; |
||||
210 | $attachmentData['real_height'] = $attachment['height']; |
||||
211 | $attachmentData['height'] = $attachment['height']; |
||||
212 | |||||
213 | // Let's see, do we want thumbs? |
||||
214 | if (!empty($modSettings['attachmentThumbnails']) |
||||
215 | && !empty($modSettings['attachmentThumbWidth']) |
||||
216 | && !empty($modSettings['attachmentThumbHeight']) |
||||
217 | && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249) |
||||
218 | { |
||||
219 | // A proper thumb doesn't exist yet? Create one! Or, it needs update. |
||||
220 | if (empty($attachment['id_thumb']) |
||||
221 | || $attachment['thumb_width'] > $modSettings['attachmentThumbWidth'] |
||||
222 | || $attachment['thumb_height'] > $modSettings['attachmentThumbHeight']) |
||||
223 | //|| ($attachment['thumb_width'] < $modSettings['attachmentThumbWidth'] && $attachment['thumb_height'] < $modSettings['attachmentThumbHeight'])) |
||||
224 | { |
||||
225 | $filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder'], false, $attachment['file_hash']); |
||||
226 | $attachment = array_merge($attachment, updateAttachmentThumbnail($filename, $attachment['id_attach'], $id_msg, $attachment['id_thumb'], $attachment['filename'])); |
||||
227 | } |
||||
228 | |||||
229 | // Only adjust dimensions on successful thumbnail creation. |
||||
230 | if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height'])) |
||||
231 | { |
||||
232 | $attachmentData['width'] = $attachment['thumb_width']; |
||||
233 | $attachmentData['height'] = $attachment['thumb_height']; |
||||
234 | } |
||||
235 | } |
||||
236 | |||||
237 | // If we have a thumbnail, make note of it! |
||||
238 | if (!empty($attachment['id_thumb'])) |
||||
239 | { |
||||
240 | $attachmentData['thumbnail'] = array( |
||||
241 | 'id' => $attachment['id_thumb'], |
||||
242 | 'href' => getUrl('action', ['action' => 'dlattach', 'topic' => $topic . '.0', 'attach' => $attachment['id_thumb'], 'image']), |
||||
243 | ); |
||||
244 | } |
||||
245 | |||||
246 | $attachmentData['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']); |
||||
247 | |||||
248 | // If thumbnails are disabled, check the maximum size of the image |
||||
249 | if (!$attachmentData['thumbnail']['has_thumb'] && ((!empty($modSettings['max_image_width']) && $attachment['width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachment['height'] > $modSettings['max_image_height']))) |
||||
250 | { |
||||
251 | if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height'])) |
||||
252 | { |
||||
253 | $attachmentData['width'] = $modSettings['max_image_width']; |
||||
254 | $attachmentData['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']); |
||||
255 | } |
||||
256 | elseif (!empty($modSettings['max_image_width'])) |
||||
257 | { |
||||
258 | $attachmentData['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']); |
||||
259 | $attachmentData['height'] = $modSettings['max_image_height']; |
||||
260 | } |
||||
261 | } |
||||
262 | elseif ($attachmentData['thumbnail']['has_thumb']) |
||||
263 | { |
||||
264 | // Data attributes for use in expandThumbLB |
||||
265 | $attachmentData['thumbnail']['lightbox'] = 'data-lightboxmessage="' . $id_msg . '" data-lightboximage="' . $attachment['id_attach'] . '"'; |
||||
266 | } |
||||
267 | |||||
268 | if (!$attachmentData['thumbnail']['has_thumb']) |
||||
269 | { |
||||
270 | $attachmentData['downloads']++; |
||||
271 | } |
||||
272 | } |
||||
273 | } |
||||
274 |