1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * Attachment display. |
||||
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 | * This file contains code covered by: |
||||
11 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||||
12 | * |
||||
13 | * @version 2.0 dev |
||||
14 | * |
||||
15 | */ |
||||
16 | |||||
17 | namespace ElkArte\Controller; |
||||
18 | |||||
19 | use ElkArte\AbstractController; |
||||
20 | use ElkArte\Action; |
||||
21 | use ElkArte\Attachments\AttachmentsDirectory; |
||||
22 | use ElkArte\Attachments\TemporaryAttachmentChunk; |
||||
23 | use ElkArte\Attachments\TemporaryAttachmentProcess; |
||||
24 | use ElkArte\Attachments\TemporaryAttachmentsList; |
||||
25 | use ElkArte\Errors\AttachmentErrorContext; |
||||
26 | use ElkArte\Exceptions\Exception; |
||||
27 | use ElkArte\Graphics\Image; |
||||
28 | use ElkArte\Graphics\TextImage; |
||||
29 | use ElkArte\Helper\FileFunctions; |
||||
30 | use ElkArte\Http\Headers; |
||||
31 | use ElkArte\Languages\Txt; |
||||
32 | use ElkArte\Themes\ThemeLoader; |
||||
33 | use ElkArte\User; |
||||
34 | |||||
35 | /** |
||||
36 | * Everything to do with attachment handling / processing |
||||
37 | * |
||||
38 | * What it does: |
||||
39 | * |
||||
40 | * - Handles the downloading of an attachment or avatar |
||||
41 | * - Handles the uploading of attachments via Ajax |
||||
42 | * - Increments the download count where applicable |
||||
43 | * |
||||
44 | 2 | */ |
|||
45 | class Attachment extends AbstractController |
||||
46 | 2 | { |
|||
47 | /** |
||||
48 | * {@inheritDoc} |
||||
49 | 2 | */ |
|||
50 | public function needTheme($action = '') |
||||
51 | { |
||||
52 | global $modSettings, $maintenance; |
||||
53 | |||||
54 | // If guests are not allowed to browse and the user is a guest... kick him! |
||||
55 | 2 | if (empty($modSettings['allow_guestAccess']) && $this->user->is_guest) |
|||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||
56 | { |
||||
57 | 2 | return true; |
|||
58 | } |
||||
59 | 2 | ||||
60 | // If not in maintenance or allowed to use the forum in maintenance |
||||
61 | if (empty($maintenance) || allowedTo('admin_forum')) |
||||
62 | { |
||||
63 | $sa = $this->_req->getQuery('sa', 'trim', ''); |
||||
64 | |||||
65 | // We will need to respond with Json |
||||
66 | return $sa === 'ulattach' || $sa === 'rmattach' || $sa === 'ulasync'; |
||||
67 | } |
||||
68 | |||||
69 | // ... politely kick them out |
||||
70 | return true; |
||||
71 | } |
||||
72 | |||||
73 | /** |
||||
74 | * {@inheritDoc} |
||||
75 | */ |
||||
76 | public function trackStats($action = '') |
||||
77 | { |
||||
78 | return false; |
||||
79 | } |
||||
80 | |||||
81 | /** |
||||
82 | * The default action is to download an attachment. |
||||
83 | * This allows ?action=attachment to be forwarded to action_dlattach() |
||||
84 | */ |
||||
85 | public function action_index() |
||||
86 | { |
||||
87 | // add a subaction array to act accordingly |
||||
88 | $subActions = array( |
||||
89 | 'dlattach' => array($this, 'action_dlattach'), |
||||
90 | 'tmpattach' => array($this, 'action_tmpattach'), |
||||
91 | 'ulattach' => array($this, 'action_ulattach'), |
||||
92 | 'ulasync' => array($this, 'action_ulasync'), |
||||
93 | 'rmattach' => array($this, 'action_rmattach'), |
||||
94 | ); |
||||
95 | |||||
96 | // Setup the action handler |
||||
97 | $action = new Action('attachments'); |
||||
98 | $subAction = $action->initialize($subActions, 'dlattach'); |
||||
99 | |||||
100 | // Call the action |
||||
101 | $action->dispatch($subAction); |
||||
102 | } |
||||
103 | |||||
104 | /** |
||||
105 | 2 | * Method to upload attachments as fragments via ajax |
|||
106 | * |
||||
107 | 2 | * - Currently called by drag drop attachment functionality |
|||
108 | * - Passed the form data with session vars |
||||
109 | 2 | * - Responds back with errors or file data |
|||
110 | 2 | * |
|||
111 | 2 | * @return bool Returns false if there was an error, otherwise true. |
|||
112 | */ |
||||
113 | public function action_ulasync() |
||||
114 | 2 | { |
|||
115 | 2 | global $context; |
|||
116 | 2 | ||||
117 | 2 | // Going to send back Json |
|||
118 | setJsonTemplate(); |
||||
119 | |||||
120 | 2 | // Final request, rebuild the file and do standard upload checks |
|||
121 | if ($this->_req->comparePost('async', 'complete', 'trim')) |
||||
122 | { |
||||
123 | $this->combineChunksAndProcess(); |
||||
124 | return true; |
||||
125 | } |
||||
126 | |||||
127 | Txt::load('Errors'); |
||||
128 | 2 | ||||
129 | // Process the chunk |
||||
130 | 2 | $chunk = new TemporaryAttachmentChunk(); |
|||
131 | $resp_data = $chunk->action_async(); |
||||
132 | 2 | ||||
133 | 2 | // If we have a PHP related upload error, set the error context |
|||
134 | if ($resp_data['result'] !== true) |
||||
135 | 2 | { |
|||
136 | $attach_errors = AttachmentErrorContext::context(); |
||||
137 | 2 | $attach_errors->activate(); |
|||
138 | if ($attach_errors->hasErrors()) |
||||
139 | 2 | { |
|||
140 | 2 | $errors = $attach_errors->prepareErrors(); |
|||
141 | foreach ($errors as $error) |
||||
142 | { |
||||
143 | $resp_data[] = $error; |
||||
144 | 2 | } |
|||
145 | |||||
146 | 2 | $context['json_data'] = array('result' => false, 'data' => $resp_data); |
|||
147 | return false; |
||||
148 | } |
||||
149 | 2 | } |
|||
150 | |||||
151 | 2 | // Set up the template details |
|||
152 | $context['json_data'] = $resp_data; |
||||
153 | |||||
154 | 2 | return true; |
|||
155 | } |
||||
156 | |||||
157 | /** |
||||
158 | * Combines the temporary attachment chunks into a single file |
||||
159 | * |
||||
160 | * This method combines the temporary attachment chunks into a single file and performs the final |
||||
161 | * processing request for the combined chunks. If the response data indicates that the result is |
||||
162 | * successful, the method passes the file off to the action_ulattach method as if it were a single file. |
||||
163 | * Otherwise, the response data is assigned to the $context['json_data'] variable. |
||||
164 | * |
||||
165 | * @return void |
||||
166 | */ |
||||
167 | private function combineChunksAndProcess() |
||||
168 | { |
||||
169 | global $context; |
||||
170 | |||||
171 | // Final chuck processing request |
||||
172 | $chunk = new TemporaryAttachmentChunk(); |
||||
173 | 2 | ||||
174 | $resp_data = $chunk->action_combineChunks(); |
||||
175 | if ($resp_data['result'] === true) |
||||
176 | { |
||||
177 | // Pass this off to action_ulattach just like it was a single file, set strict as false as we already have the |
||||
178 | // combined chunks in the attachment directory and we don't need to verify it was a php upload any longer |
||||
179 | 2 | $this->action_ulattach(false); |
|||
180 | } |
||||
181 | 2 | else |
|||
182 | { |
||||
183 | $context['json_data'] = $resp_data; |
||||
184 | } |
||||
185 | } |
||||
186 | |||||
187 | /** |
||||
188 | * Method to upload attachments via ajax |
||||
189 | * |
||||
190 | * - Currently called by drag drop attachment functionality |
||||
191 | * - Passed the form data with session vars |
||||
192 | * - Responds back with errors or file data |
||||
193 | * |
||||
194 | * @param bool $strict True if attachment processing should use move_uploaded_file, rename otherwise. Default is true. |
||||
195 | * |
||||
196 | * @return bool|void False if the session is invalid or an error occurred, void otherwise. |
||||
197 | */ |
||||
198 | public function action_ulattach($strict = true) |
||||
199 | { |
||||
200 | global $context, $modSettings, $txt; |
||||
201 | |||||
202 | $resp_data = array(); |
||||
203 | Txt::load('Errors'); |
||||
204 | $context['attachments']['can']['post'] = !empty($modSettings['attachmentEnable']) && (int) $modSettings['attachmentEnable'] === 1 && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments'))); |
||||
205 | |||||
206 | // Set up the template details |
||||
207 | setJsonTemplate(); |
||||
208 | |||||
209 | // Make sure the session is still valid |
||||
210 | if (checkSession('post', '', false) !== '') |
||||
211 | { |
||||
212 | $context['json_data'] = array('result' => false, 'data' => $txt['session_timeout_file_upload']); |
||||
213 | |||||
214 | return false; |
||||
215 | } |
||||
216 | |||||
217 | // We should have files, otherwise why are we here? |
||||
218 | if (isset($_FILES['attachment'])) |
||||
219 | { |
||||
220 | Txt::load('Post'); |
||||
221 | |||||
222 | $attach_errors = AttachmentErrorContext::context(); |
||||
223 | $attach_errors->activate(); |
||||
224 | |||||
225 | if ($context['attachments']['can']['post'] && empty($this->_req->post->from_qr)) |
||||
226 | { |
||||
227 | $processAttachments = new TemporaryAttachmentProcess(); |
||||
228 | $processAttachments->strict = $strict; |
||||
229 | $processAttachments->processAttachments($this->_req->getPost('msg', 'intval', 0)); |
||||
230 | } |
||||
231 | |||||
232 | // Any mistakes? |
||||
233 | if ($attach_errors->hasErrors()) |
||||
234 | { |
||||
235 | $errors = $attach_errors->prepareErrors(); |
||||
236 | |||||
237 | // Bad news for you, the attachments did not process, lets tell them why |
||||
238 | foreach ($errors as $error) |
||||
239 | { |
||||
240 | $resp_data[] = $error; |
||||
241 | } |
||||
242 | |||||
243 | $context['json_data'] = array('result' => false, 'data' => $resp_data); |
||||
244 | } |
||||
245 | // No errors, lets get the details of what we have for our response back to the upload dialog |
||||
246 | else |
||||
247 | { |
||||
248 | $tmp_attachments = new TemporaryAttachmentsList(); |
||||
249 | foreach ($tmp_attachments->toArray() as $val) |
||||
250 | { |
||||
251 | // We need to grab the name anyhow |
||||
252 | if (!empty($val['tmp_name'])) |
||||
253 | { |
||||
254 | $resp_data = array( |
||||
255 | 'name' => $val['name'], |
||||
256 | 'attachid' => $val['public_attachid'], |
||||
257 | 'size' => $val['size'], |
||||
258 | 'resized' => !empty($val['resized']), |
||||
259 | ); |
||||
260 | } |
||||
261 | } |
||||
262 | |||||
263 | $context['json_data'] = ['result' => true, 'data' => $resp_data]; |
||||
264 | } |
||||
265 | } |
||||
266 | // Could not find the files you claimed to have sent |
||||
267 | else |
||||
268 | { |
||||
269 | $context['json_data'] = ['result' => false, 'data' => $txt['no_files_uploaded']]; |
||||
270 | } |
||||
271 | } |
||||
272 | |||||
273 | /** |
||||
274 | * Function to remove temporary attachments which were newly added via ajax calls |
||||
275 | * or to remove previous saved ones from an existing post |
||||
276 | * |
||||
277 | * What it does: |
||||
278 | * |
||||
279 | * - Currently called by drag drop attachment functionality |
||||
280 | * - Requires file name and file path |
||||
281 | * - Responds back with success or error |
||||
282 | */ |
||||
283 | public function action_rmattach() |
||||
284 | { |
||||
285 | global $context, $txt; |
||||
286 | |||||
287 | // Prepare the template so we can respond with json |
||||
288 | setJsonTemplate(); |
||||
289 | |||||
290 | // Make sure the session is valid |
||||
291 | if (checkSession('post', '', false) !== '') |
||||
292 | { |
||||
293 | Txt::load('Errors'); |
||||
294 | $context['json_data'] = array('result' => false, 'data' => $txt['session_timeout']); |
||||
295 | |||||
296 | return false; |
||||
297 | } |
||||
298 | |||||
299 | // We need a filename and path, or we are not going any further |
||||
300 | if (isset($this->_req->post->attachid)) |
||||
301 | { |
||||
302 | $result = false; |
||||
303 | $tmp_attachments = new TemporaryAttachmentsList(); |
||||
304 | if ($tmp_attachments->hasAttachments()) |
||||
305 | { |
||||
306 | $attachId = $tmp_attachments->getIdFromPublic($this->_req->post->attachid); |
||||
307 | |||||
308 | try |
||||
309 | { |
||||
310 | $tmp_attachments->removeById($attachId); |
||||
311 | $context['json_data'] = array('result' => true); |
||||
312 | $result = true; |
||||
313 | } |
||||
314 | catch (\Exception $e) |
||||
315 | { |
||||
316 | $result = $e->getMessage(); |
||||
317 | } |
||||
318 | } |
||||
319 | |||||
320 | // Not a temporary attachment, but a previously uploaded one? |
||||
321 | if ($result !== true) |
||||
322 | { |
||||
323 | require_once(SUBSDIR . '/ManageAttachments.subs.php'); |
||||
324 | $attachId = $this->_req->getPost('attachid', 'intval'); |
||||
325 | if (canRemoveAttachment($attachId, User::$info->id)) |
||||
326 | { |
||||
327 | $result_tmp = removeAttachments(array('id_attach' => $attachId), '', true); |
||||
328 | if (!empty($result_tmp)) |
||||
329 | { |
||||
330 | $context['json_data'] = array('result' => true); |
||||
331 | $result = true; |
||||
332 | } |
||||
333 | else |
||||
334 | { |
||||
335 | $result = $result_tmp; |
||||
336 | } |
||||
337 | } |
||||
338 | } |
||||
339 | |||||
340 | if ($result !== true) |
||||
341 | { |
||||
342 | Txt::load('Errors'); |
||||
343 | $context['json_data'] = ['result' => false, 'data' => $txt[empty($result) ? 'attachment_not_found' : $result]]; |
||||
344 | } |
||||
345 | } |
||||
346 | else |
||||
347 | { |
||||
348 | Txt::load('Errors'); |
||||
349 | $context['json_data'] = ['result' => false, 'data' => $txt['attachment_not_found']]; |
||||
350 | } |
||||
351 | } |
||||
352 | |||||
353 | /** |
||||
354 | * Downloads an attachment or avatar, and increments the download count. |
||||
355 | * |
||||
356 | * What it does: |
||||
357 | * |
||||
358 | * - It requires the view_attachments permission. (not for avatars!) |
||||
359 | * - It disables the session parser, and clears any previous output. |
||||
360 | * - It is accessed via the query string ?action=dlattach. |
||||
361 | * - Views to attachments and avatars do not increase hits and are not logged |
||||
362 | * in the "Who's Online" log. |
||||
363 | * |
||||
364 | * @throws Exception |
||||
365 | */ |
||||
366 | public function action_dlattach() |
||||
367 | { |
||||
368 | global $modSettings, $context, $topic, $board, $settings; |
||||
369 | |||||
370 | // Some defaults that we need. |
||||
371 | $context['no_last_modified'] = true; |
||||
372 | $filename = null; |
||||
373 | |||||
374 | // Make sure some attachment was requested! |
||||
375 | if (!isset($this->_req->query->attach)) |
||||
376 | { |
||||
377 | if (!isset($this->_req->query->id)) |
||||
378 | { |
||||
379 | // Give them the old can't find it image |
||||
380 | $this->action_text_to_image('attachment_not_found'); |
||||
381 | } |
||||
382 | |||||
383 | if ($this->_req->query->id === 'ila') |
||||
384 | { |
||||
385 | // Give them the old can't touch this |
||||
386 | $this->action_text_to_image(($this->user->is_guest ? 'not_applicable' : 'awaiting_approval'), 90, 90, true); |
||||
0 ignored issues
–
show
The property
is_guest does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
387 | } |
||||
388 | } |
||||
389 | |||||
390 | // We need to do some work on attachments and avatars. |
||||
391 | require_once(SUBSDIR . '/Attachments.subs.php'); |
||||
392 | |||||
393 | // Temporary attachment, special case... |
||||
394 | if (isset($this->_req->query->attach) && strpos($this->_req->query->attach, 'post_tmp_' . $this->user->id . '_') !== false) |
||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
395 | { |
||||
396 | // Return via tmpattach, back presumably to the post form |
||||
397 | $this->action_tmpattach(); |
||||
398 | } |
||||
399 | |||||
400 | $id_attach = $this->_req->getQuery('attach', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
||||
401 | |||||
402 | // This is just a regular attachment... Avatars are no longer a dlattach option |
||||
403 | if (empty($topic) && !empty($id_attach)) |
||||
404 | { |
||||
405 | $id_board = 0; |
||||
406 | $id_topic = 0; |
||||
407 | $attachPos = getAttachmentPosition($id_attach); |
||||
408 | if ($attachPos !== false) |
||||
409 | { |
||||
410 | [$id_board, $id_topic] = array_values($attachPos); |
||||
0 ignored issues
–
show
It seems like
$attachPos can also be of type true ; however, parameter $array of array_values() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
411 | } |
||||
412 | } |
||||
413 | else |
||||
414 | { |
||||
415 | $id_board = $board; |
||||
416 | $id_topic = $topic; |
||||
417 | } |
||||
418 | |||||
419 | isAllowedTo('view_attachments', $id_board); |
||||
420 | |||||
421 | if ($this->_req->getQuery('thumb') === null) |
||||
422 | { |
||||
423 | $attachment = getAttachmentFromTopic($id_attach, $id_topic); |
||||
424 | } |
||||
425 | else |
||||
426 | { |
||||
427 | $this->_req->query->image = true; |
||||
428 | $attachment = getAttachmentThumbFromTopic($id_attach, $id_topic); |
||||
429 | |||||
430 | // No file name, no thumbnail, no image. |
||||
431 | if (empty($attachment['filename'])) |
||||
432 | { |
||||
433 | $full_attach = getAttachmentFromTopic($id_attach, $id_topic); |
||||
434 | $attachment['filename'] = empty($full_attach['filename']) ? '' : $full_attach['filename']; |
||||
435 | $attachment['id_attach'] = 0; |
||||
436 | $attachment['attachment_type'] = 0; |
||||
437 | $attachment['approved'] = $full_attach['approved'] ?? 0; |
||||
438 | $attachment['id_member'] = $full_attach['id_member']; |
||||
439 | |||||
440 | // If it is a known extension, show a mimetype extension image |
||||
441 | $check = returnMimeThumb(empty($full_attach['fileext']) ? 'default' : $full_attach['fileext']); |
||||
442 | if ($check !== false) |
||||
0 ignored issues
–
show
|
|||||
443 | { |
||||
444 | $attachment['fileext'] = 'png'; |
||||
445 | $attachment['mime_type'] = 'image/png'; |
||||
446 | $filename = $check; |
||||
447 | } |
||||
448 | else |
||||
449 | { |
||||
450 | $attachmentsDir = new AttachmentsDirectory($modSettings, database()); |
||||
451 | $filename = $attachmentsDir->getCurrent() . '/' . $attachment['filename']; |
||||
452 | } |
||||
453 | |||||
454 | if (strpos(getMimeType($filename), 'image') !== 0) |
||||
455 | { |
||||
456 | $attachment['fileext'] = 'png'; |
||||
457 | $attachment['mime_type'] = 'image/png'; |
||||
458 | $filename = $settings['theme_dir'] . '/images/mime_images/default.png'; |
||||
459 | } |
||||
460 | } |
||||
461 | } |
||||
462 | |||||
463 | if (empty($attachment)) |
||||
464 | { |
||||
465 | // Exit via action_text_to_image |
||||
466 | $this->action_text_to_image('attachment_not_found'); |
||||
467 | } |
||||
468 | |||||
469 | $id_folder = $attachment['id_folder'] ?? ''; |
||||
470 | $real_filename = $attachment['filename'] ?? ''; |
||||
471 | $file_hash = $attachment['file_hash'] ?? ''; |
||||
472 | $file_ext = $attachment['fileext'] ?? ''; |
||||
473 | $id_attach = $attachment['id_attach'] ?? -1; |
||||
474 | $attachment_type = $attachment['attachment_type'] ?? -1; |
||||
475 | $mime_type = $attachment['mime_type'] ?? ''; |
||||
476 | $is_approved = $attachment['approved'] ?? ''; |
||||
477 | $id_member = $attachment['id_member'] ?? ''; |
||||
478 | |||||
479 | // If it isn't yet approved, do they have permission to view it? |
||||
480 | if (!$is_approved && ($id_member === 0 || $this->user->id !== $id_member) && ($attachment_type === 0 || $attachment_type === 3)) |
||||
481 | { |
||||
482 | isAllowedTo('approve_posts', $id_board ?? $board); |
||||
483 | } |
||||
484 | |||||
485 | // Update the download counter (unless it's a thumbnail). |
||||
486 | if (!empty($id_attach && $attachment_type != 3)) |
||||
487 | { |
||||
488 | increaseDownloadCounter($id_attach); |
||||
489 | } |
||||
490 | |||||
491 | if ($filename === null) |
||||
492 | { |
||||
493 | $filename = getAttachmentFilename($real_filename, $id_attach, $id_folder, false, $file_hash); |
||||
494 | } |
||||
495 | |||||
496 | $eTag = '"' . substr($id_attach . $real_filename . @filemtime($filename), 0, 64) . '"'; |
||||
0 ignored issues
–
show
Are you sure
@filemtime($filename) of type false|integer can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
497 | $disposition = isset($this->_req->query->image) ? 'inline' : 'attachment'; |
||||
498 | $do_cache = !(!isset($this->_req->query->image) && getValidMimeImageType($file_ext) !== ''); |
||||
499 | |||||
500 | // Make sure the mime type warrants an inline display. |
||||
501 | if (isset($this->_req->query->image) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0) |
||||
502 | { |
||||
503 | unset($this->_req->query->image); |
||||
504 | $mime_type = ''; |
||||
505 | } |
||||
506 | // Does this have a mime type? |
||||
507 | elseif (empty($mime_type) || (!isset($this->_req->query->image) && getValidMimeImageType($file_ext) !== '')) |
||||
508 | { |
||||
509 | $mime_type = ''; |
||||
510 | if (isset($this->_req->query->image)) |
||||
511 | { |
||||
512 | unset($this->_req->query->image); |
||||
513 | } |
||||
514 | } |
||||
515 | |||||
516 | $this->prepare_headers($filename, $eTag, $mime_type, $disposition, $real_filename, $do_cache); |
||||
517 | $this->send_file($filename, $mime_type); |
||||
518 | |||||
519 | obExit(false); |
||||
520 | } |
||||
521 | |||||
522 | /** |
||||
523 | * Generates a language image based on text for display, outputs that image and exits |
||||
524 | * |
||||
525 | * @param null|string $text if null will use default attachment not found string |
||||
526 | * @param int $width If set, defines the width of the image, text font size will be scaled to fit |
||||
527 | * @param int $height If set, defines the height of the image |
||||
528 | * @param bool $split If true will break text strings so all words are separated by newlines |
||||
529 | * @throws Exception |
||||
530 | */ |
||||
531 | public function action_text_to_image($text = null, $width = 200, $height = 75, $split = false) |
||||
532 | { |
||||
533 | global $txt; |
||||
534 | |||||
535 | new ThemeLoader(); |
||||
536 | Txt::load('Errors'); |
||||
537 | $text = $text === null ? $txt['attachment_not_found'] : $txt[$text] ?? $text; |
||||
538 | $text = $split ? str_replace(' ', "\n", $text) : $text; |
||||
539 | |||||
540 | try |
||||
541 | { |
||||
542 | $img = new TextImage($text); |
||||
543 | $img = $img->generate($width, $height); |
||||
544 | } |
||||
545 | catch (\Exception) |
||||
546 | { |
||||
547 | throw new Exception('no_access', false); |
||||
548 | } |
||||
549 | |||||
550 | $this->prepare_headers('no_image', 'no_image', 'image/png', 'inline', 'no_image.png', true, false); |
||||
551 | Headers::instance()->sendHeaders(); |
||||
552 | echo $img; |
||||
553 | |||||
554 | obExit(false); |
||||
555 | } |
||||
556 | |||||
557 | /** |
||||
558 | * If the mime type benefits from compression e.g. text/xyz and gzencode is |
||||
559 | * available and the user agent accepts gzip, then return true, else false |
||||
560 | * |
||||
561 | * @param string $mime_type |
||||
562 | * @return bool if we should compress the file |
||||
563 | */ |
||||
564 | public function useCompression($mime_type) |
||||
565 | { |
||||
566 | global $modSettings; |
||||
567 | |||||
568 | // Not compressible, or not supported / requested by client |
||||
569 | if (!preg_match('~^(?:text/|application/(?:json|xml|rss\+xml)$)~i', $mime_type) |
||||
570 | || (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) || strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === false)) |
||||
571 | { |
||||
572 | return false; |
||||
573 | } |
||||
574 | |||||
575 | // Support is available on the serve |
||||
576 | return !(!function_exists('gzencode') && !empty($modSettings['enableCompressedOutput'])); |
||||
577 | } |
||||
578 | |||||
579 | /** |
||||
580 | * Takes care of sending out the most common headers. |
||||
581 | * |
||||
582 | * @param string $filename Full path+file name of the file in the filesystem |
||||
583 | * @param string $eTag ETag cache validator |
||||
584 | * @param string $mime_type The mime-type of the file |
||||
585 | * @param string $disposition The value of the Content-Disposition header |
||||
586 | * @param string $real_filename The original name of the file |
||||
587 | * @param bool $do_cache Send a max-age header or not |
||||
588 | * @param bool $check_filename When false, any check on $filename is skipped |
||||
589 | */ |
||||
590 | public function prepare_headers($filename, $eTag, $mime_type, $disposition, $real_filename, $do_cache, $check_filename = true) |
||||
591 | { |
||||
592 | global $txt; |
||||
593 | |||||
594 | $headers = Headers::instance(); |
||||
595 | $protocol = detectServer()->getProtocol(); |
||||
596 | |||||
597 | // No point in a nicer message, because this is supposed to be an attachment anyway... |
||||
598 | if ($check_filename && !FileFunctions::instance()->fileExists($filename)) |
||||
599 | { |
||||
600 | Txt::load('Errors'); |
||||
601 | |||||
602 | $headers |
||||
603 | ->removeHeader('all') |
||||
604 | ->headerSpecial($protocol . ' 404 Not Found') |
||||
605 | ->sendHeaders(); |
||||
606 | |||||
607 | // We need to die like this *before* we send any anti-caching headers as below. |
||||
608 | die('404 - ' . $txt['attachment_not_found']); |
||||
0 ignored issues
–
show
|
|||||
609 | } |
||||
610 | |||||
611 | // If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again. |
||||
612 | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) |
||||
613 | { |
||||
614 | [$modified_since] = explode(';', $this->_req->server->HTTP_IF_MODIFIED_SINCE); |
||||
615 | if (!$check_filename || strtotime($modified_since) >= filemtime($filename)) |
||||
616 | { |
||||
617 | @ob_end_clean(); |
||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
ob_end_clean() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
618 | |||||
619 | // Answer the question - no, it hasn't been modified ;). |
||||
620 | $headers |
||||
621 | ->removeHeader('all') |
||||
622 | ->headerSpecial($protocol . ' 304 Not Modified') |
||||
623 | ->sendHeaders(); |
||||
624 | exit; |
||||
0 ignored issues
–
show
|
|||||
625 | } |
||||
626 | } |
||||
627 | |||||
628 | // Check whether the ETag was sent back, and cache based on that... |
||||
629 | if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false) |
||||
630 | { |
||||
631 | @ob_end_clean(); |
||||
632 | |||||
633 | $headers |
||||
634 | ->removeHeader('all') |
||||
635 | ->headerSpecial($protocol . ' 304 Not Modified') |
||||
636 | ->sendHeaders(); |
||||
637 | exit; |
||||
0 ignored issues
–
show
|
|||||
638 | } |
||||
639 | |||||
640 | // Send the attachment headers. |
||||
641 | $headers |
||||
642 | ->header('Expires', gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT') |
||||
643 | ->header('Last-Modified', gmdate('D, d M Y H:i:s', $check_filename ? filemtime($filename) : time() - 525600 * 60) . ' GMT') |
||||
644 | ->header('Accept-Ranges', 'bytes') |
||||
645 | ->header('Connection', 'close') |
||||
646 | ->header('ETag', $eTag); |
||||
647 | |||||
648 | // Different browsers like different standards... |
||||
649 | $headers->setAttachmentFileParams($mime_type, $real_filename, $disposition); |
||||
650 | |||||
651 | // If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE. |
||||
652 | if ($do_cache) |
||||
653 | { |
||||
654 | $headers |
||||
655 | ->header('Cache-Control', 'max-age=' . (525600 * 60) . ', private'); |
||||
656 | } |
||||
657 | else |
||||
658 | { |
||||
659 | $headers |
||||
660 | ->header('Pragma', 'no-cache') |
||||
661 | ->header('Cache-Control', 'no-cache'); |
||||
662 | } |
||||
663 | |||||
664 | // Try to buy some time... |
||||
665 | detectServer()->setTimeLimit(600); |
||||
666 | } |
||||
667 | |||||
668 | /** |
||||
669 | * Sends the requested file to the user. If the file is compressible e.g. |
||||
670 | * has a mine type of text/??? may compress the file prior to sending. |
||||
671 | * |
||||
672 | * @param string $filename |
||||
673 | * @param string $mime_type |
||||
674 | */ |
||||
675 | public function send_file($filename, $mime_type) |
||||
676 | { |
||||
677 | $headers = Headers::instance(); |
||||
678 | $body = file_get_contents($filename); |
||||
679 | $length = FileFunctions::instance()->fileSize($filename); |
||||
680 | $use_compression = $this->useCompression($mime_type); |
||||
681 | |||||
682 | // If we can/should compress this file |
||||
683 | if ($use_compression && strlen($body) > 250) |
||||
684 | { |
||||
685 | $body = gzencode($body, 2); |
||||
686 | $length = strlen($body); |
||||
687 | $headers |
||||
688 | ->header('Content-Encoding', 'gzip') |
||||
689 | ->header('Vary', 'Accept-Encoding'); |
||||
690 | } |
||||
691 | |||||
692 | if (!empty($length)) |
||||
693 | { |
||||
694 | $headers->header('Content-Length', $length); |
||||
695 | } |
||||
696 | |||||
697 | // Forcibly end any output buffering going on. |
||||
698 | while (ob_get_level() > 0) |
||||
699 | { |
||||
700 | @ob_end_clean(); |
||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
ob_end_clean() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
701 | } |
||||
702 | |||||
703 | // Someone is getting a present |
||||
704 | $headers->send(); |
||||
705 | echo $body; |
||||
706 | } |
||||
707 | |||||
708 | /** |
||||
709 | * "Simplified", cough, version of action_dlattach to send out thumbnails while creating |
||||
710 | * or editing a message. |
||||
711 | */ |
||||
712 | public function action_tmpattach() |
||||
713 | { |
||||
714 | global $modSettings, $topic; |
||||
715 | |||||
716 | // Make sure some attachment was requested! |
||||
717 | if (!isset($this->_req->query->attach)) |
||||
718 | { |
||||
719 | $this->action_text_to_image('attachment_not_found'); |
||||
720 | } |
||||
721 | |||||
722 | // We will need some help |
||||
723 | require_once(SUBSDIR . '/Attachments.subs.php'); |
||||
724 | $tmp_attachments = new TemporaryAttachmentsList(); |
||||
725 | $attachmentsDir = new AttachmentsDirectory($modSettings, database()); |
||||
726 | |||||
727 | try |
||||
728 | { |
||||
729 | if (empty($topic) || (string) (int) $this->_req->query->attach !== (string) $this->_req->query->attach) |
||||
730 | { |
||||
731 | $attach_data = $tmp_attachments->getTempAttachById($this->_req->query->attach, $attachmentsDir, User::$info->id); |
||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
732 | $file_ext = pathinfo($attach_data['name'], PATHINFO_EXTENSION); |
||||
733 | $filename = $attach_data['tmp_name']; |
||||
734 | $id_attach = $attach_data['attachid']; |
||||
735 | $real_filename = $attach_data['name']; |
||||
736 | $mime_type = $attach_data['type']; |
||||
737 | } |
||||
738 | else |
||||
739 | { |
||||
740 | $id_attach = $this->_req->getQuery('attach', 'intval', -1); |
||||
741 | |||||
742 | isAllowedTo('view_attachments'); |
||||
743 | $attachment = getAttachmentFromTopic($id_attach, $topic); |
||||
744 | if (empty($attachment)) |
||||
745 | { |
||||
746 | // Exit via action_text_to_image |
||||
747 | $this->action_text_to_image('attachment_not_found'); |
||||
748 | } |
||||
749 | |||||
750 | // Save some typing |
||||
751 | $id_folder = $attachment['id_folder']; |
||||
752 | $real_filename = $attachment['filename']; |
||||
753 | $file_hash = $attachment['file_hash']; |
||||
754 | $file_ext = $attachment['fileext']; |
||||
755 | $id_attach = $attachment['id_attach']; |
||||
756 | $attachment_type = (int) $attachment['attachment_type']; |
||||
757 | $mime_type = $attachment['mime_type']; |
||||
758 | $is_approved = $attachment['approved']; |
||||
759 | $id_member = (int) $attachment['id_member']; |
||||
760 | |||||
761 | // If it isn't yet approved, do they have permission to view it? |
||||
762 | if (!$is_approved && ($id_member === 0 || $this->user->id !== $id_member) |
||||
763 | && ($attachment_type === 0 || $attachment_type === 3)) |
||||
764 | { |
||||
765 | isAllowedTo('approve_posts'); |
||||
766 | } |
||||
767 | |||||
768 | $filename = getAttachmentFilename($real_filename, $id_attach, $id_folder, false, $file_hash); |
||||
769 | } |
||||
770 | } |
||||
771 | catch (\Exception $exception) |
||||
772 | { |
||||
773 | throw new Exception($exception->getMessage(), false); |
||||
774 | } |
||||
775 | |||||
776 | $resize = true; |
||||
777 | |||||
778 | // Return mime type ala mimetype extension |
||||
779 | if (strpos(getMimeType($filename), 'image') !== 0) |
||||
780 | { |
||||
781 | $checkMime = returnMimeThumb($file_ext); |
||||
782 | $mime_type = 'image/png'; |
||||
783 | $resize = false; |
||||
784 | $filename = $checkMime; |
||||
785 | } |
||||
786 | |||||
787 | $eTag = '"' . substr($id_attach . $real_filename . filemtime($filename), 0, 64) . '"'; |
||||
788 | $do_cache = !(!isset($this->_req->query->image) && getValidMimeImageType($file_ext) !== ''); |
||||
789 | |||||
790 | $this->prepare_headers($filename, $eTag, $mime_type, 'inline', $real_filename, $do_cache); |
||||
791 | |||||
792 | // do not resize for ;image |
||||
793 | if ($resize && !isset($this->_req->query->ila, $this->_req->query->image)) |
||||
794 | { |
||||
795 | // Create a thumbnail image |
||||
796 | $image = new Image($filename); |
||||
797 | |||||
798 | $filename .= '_thumb'; |
||||
799 | $max_width = $this->_req->isSet('thumb') && !empty($modSettings['attachmentThumbWidth']) ? $modSettings['attachmentThumbWidth'] : 300; |
||||
800 | $max_height = $this->_req->isSet('thumb') && !empty($modSettings['attachmentThumbHeight']) ? $modSettings['attachmentThumbHeight'] : 300; |
||||
801 | |||||
802 | $image->createThumbnail($max_width, $max_height, $filename, null, false); |
||||
803 | } |
||||
804 | |||||
805 | // With the headers complete, send the file data |
||||
806 | $this->send_file($filename, $mime_type); |
||||
807 | |||||
808 | obExit(false); |
||||
809 | } |
||||
810 | } |
||||
811 |