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