Yoshi2889 /
SMF2.1
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file contains handling attachments. |
||
| 5 | * |
||
| 6 | * Simple Machines Forum (SMF) |
||
| 7 | * |
||
| 8 | * @package SMF |
||
| 9 | * @author Simple Machines https://www.simplemachines.org |
||
| 10 | * @copyright 2020 Simple Machines and individual contributors |
||
| 11 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||
| 12 | * |
||
| 13 | * @version 2.1 RC2 |
||
| 14 | */ |
||
| 15 | |||
| 16 | if (!defined('SMF')) |
||
| 17 | die('No direct access...'); |
||
| 18 | |||
| 19 | class Attachments |
||
| 20 | { |
||
| 21 | protected $_msg = 0; |
||
| 22 | protected $_board = null; |
||
| 23 | protected $_attachmentUploadDir = false; |
||
| 24 | protected $_attchDir = ''; |
||
| 25 | protected $_currentAttachmentUploadDir; |
||
| 26 | protected $_canPostAttachment; |
||
| 27 | protected $_generalErrors = array(); |
||
| 28 | protected $_initialError; |
||
| 29 | protected $_attachments = array(); |
||
| 30 | protected $_attachResults = array(); |
||
| 31 | protected $_attachSuccess = array(); |
||
| 32 | protected $_response = array( |
||
| 33 | 'error' => true, |
||
| 34 | 'data' => array(), |
||
| 35 | 'extra' => '', |
||
| 36 | ); |
||
| 37 | protected $_subActions = array( |
||
| 38 | 'add', |
||
| 39 | 'delete', |
||
| 40 | ); |
||
| 41 | protected $_sa = false; |
||
| 42 | |||
| 43 | public function __construct() |
||
| 44 | { |
||
| 45 | global $modSettings, $context; |
||
| 46 | |||
| 47 | $this->_msg = (int) !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0; |
||
| 48 | $this->_board = (int) !empty($_REQUEST['board']) ? $_REQUEST['board'] : null; |
||
| 49 | |||
| 50 | $this->_currentAttachmentUploadDir = $modSettings['currentAttachmentUploadDir']; |
||
| 51 | |||
| 52 | $this->_attachmentUploadDir = $modSettings['attachmentUploadDir']; |
||
| 53 | |||
| 54 | $this->_attchDir = $context['attach_dir'] = $this->_attachmentUploadDir[$modSettings['currentAttachmentUploadDir']]; |
||
| 55 | |||
| 56 | $this->_canPostAttachment = $context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment', $this->_board) || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments', $this->_board))); |
||
| 57 | } |
||
| 58 | |||
| 59 | public function call() |
||
| 60 | { |
||
| 61 | global $smcFunc, $sourcedir; |
||
| 62 | |||
| 63 | require_once($sourcedir . '/Subs-Attachments.php'); |
||
| 64 | |||
| 65 | // Guest aren't welcome, sorry. |
||
| 66 | is_not_guest(); |
||
| 67 | |||
| 68 | // Need this. For reasons... |
||
| 69 | loadLanguage('Post'); |
||
| 70 | |||
| 71 | $this->_sa = !empty($_REQUEST['sa']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_REQUEST['sa'])) : false; |
||
| 72 | |||
| 73 | if ($this->_canPostAttachment && $this->_sa && in_array($this->_sa, $this->_subActions)) |
||
| 74 | $this->{$this->_sa}(); |
||
| 75 | |||
| 76 | // Just send a generic message. |
||
| 77 | else |
||
| 78 | $this->setResponse(array( |
||
| 79 | 'text' => $this->_sa == 'add' ? 'attach_error_title' : 'attached_file_deleted_error', |
||
| 80 | 'type' => 'error', |
||
| 81 | 'data' => false, |
||
| 82 | )); |
||
| 83 | |||
| 84 | // Back to the future, oh, to the browser! |
||
| 85 | $this->sendResponse(); |
||
| 86 | } |
||
| 87 | |||
| 88 | public function delete() |
||
| 89 | { |
||
| 90 | global $sourcedir; |
||
| 91 | |||
| 92 | // Need this, don't ask why just nod your head. |
||
| 93 | require_once($sourcedir . '/ManageAttachments.php'); |
||
| 94 | |||
| 95 | $attachID = !empty($_REQUEST['attach']) && is_numeric($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : 0; |
||
| 96 | |||
| 97 | // Need something to work with. |
||
| 98 | if (!$attachID || (!empty($_SESSION['already_attached']) && !isset($_SESSION['already_attached'][$attachID]))) |
||
| 99 | return $this->setResponse(array( |
||
| 100 | 'text' => 'attached_file_deleted_error', |
||
| 101 | 'type' => 'error', |
||
| 102 | 'data' => false, |
||
| 103 | )); |
||
| 104 | |||
| 105 | // Lets pass some params and see what happens :P |
||
| 106 | $affectedMessage = removeAttachments(array('id_attach' => $attachID), '', true, true); |
||
| 107 | |||
| 108 | // Gotta also remove the attachment from the session var. |
||
| 109 | unset($_SESSION['already_attached'][$attachID]); |
||
| 110 | |||
| 111 | // $affectedMessage returns an empty array array(0) which php treats as non empty... awesome... |
||
| 112 | $this->setResponse(array( |
||
| 113 | 'text' => !empty($affectedMessage) ? 'attached_file_deleted' : 'attached_file_deleted_error', |
||
| 114 | 'type' => !empty($affectedMessage) ? 'info' : 'warning', |
||
| 115 | 'data' => $affectedMessage, |
||
| 116 | )); |
||
| 117 | } |
||
| 118 | |||
| 119 | public function add() |
||
| 120 | { |
||
| 121 | // You gotta be able to post attachments. |
||
| 122 | if (!$this->_canPostAttachment) |
||
| 123 | return $this->setResponse(array( |
||
| 124 | 'text' => 'attached_file_cannot', |
||
| 125 | 'type' => 'error', |
||
| 126 | 'data' => false, |
||
| 127 | )); |
||
| 128 | |||
| 129 | // Process them at once! |
||
| 130 | $this->processAttachments(); |
||
| 131 | |||
| 132 | // The attachments was created and moved the the right folder, time to update the DB. |
||
| 133 | if (!empty($_SESSION['temp_attachments'])) |
||
| 134 | $this->createAttach(); |
||
| 135 | |||
| 136 | // Set the response. |
||
| 137 | $this->setResponse(); |
||
| 138 | } |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Moves an attachment to the proper directory and set the relevant data into $_SESSION['temp_attachments'] |
||
| 142 | */ |
||
| 143 | protected function processAttachments() |
||
| 144 | { |
||
| 145 | global $context, $modSettings, $smcFunc, $user_info, $txt; |
||
| 146 | |||
| 147 | if (!isset($_FILES['attachment']['name'])) |
||
| 148 | $_FILES['attachment']['tmp_name'] = array(); |
||
| 149 | |||
| 150 | // If there are attachments, calculate the total size and how many. |
||
| 151 | $context['attachments']['total_size'] = 0; |
||
| 152 | $context['attachments']['quantity'] = 0; |
||
| 153 | |||
| 154 | // If this isn't a new post, check the current attachments. |
||
| 155 | if (isset($_REQUEST['msg'])) |
||
| 156 | { |
||
| 157 | $context['attachments']['quantity'] = count($context['current_attachments']); |
||
| 158 | foreach ($context['current_attachments'] as $attachment) |
||
| 159 | $context['attachments']['total_size'] += $attachment['size']; |
||
| 160 | } |
||
| 161 | |||
| 162 | // A bit of house keeping first. |
||
| 163 | if (!empty($_SESSION['temp_attachments']) && count($_SESSION['temp_attachments']) == 1) |
||
| 164 | unset($_SESSION['temp_attachments']); |
||
| 165 | |||
| 166 | // Our infamous SESSION var, we are gonna have soo much fun with it! |
||
| 167 | if (!isset($_SESSION['temp_attachments'])) |
||
| 168 | $_SESSION['temp_attachments'] = array(); |
||
| 169 | |||
| 170 | // Make sure we're uploading to the right place. |
||
| 171 | if (!empty($modSettings['automanage_attachments'])) |
||
| 172 | automanage_attachments_check_directory(); |
||
| 173 | |||
| 174 | // Is the attachments folder actually there? |
||
| 175 | if (!empty($context['dir_creation_error'])) |
||
| 176 | $this->_generalErrors[] = $context['dir_creation_error']; |
||
| 177 | |||
| 178 | // The current attach folder ha some issues... |
||
| 179 | elseif (!is_dir($this->_attchDir)) |
||
| 180 | { |
||
| 181 | $this->_generalErrors[] = 'attach_folder_warning'; |
||
| 182 | log_error(sprintf($txt['attach_folder_admin_warning'], $this->_attchDir), 'critical'); |
||
| 183 | } |
||
| 184 | |||
| 185 | // If this isn't a new post, check the current attachments. |
||
| 186 | if (empty($this->_generalErrors) && $this->_msg) |
||
| 187 | { |
||
| 188 | $context['attachments'] = array(); |
||
| 189 | $request = $smcFunc['db_query']('', ' |
||
| 190 | SELECT COUNT(*), SUM(size) |
||
| 191 | FROM {db_prefix}attachments |
||
| 192 | WHERE id_msg = {int:id_msg} |
||
| 193 | AND attachment_type = {int:attachment_type}', |
||
| 194 | array( |
||
| 195 | 'id_msg' => (int) $this->_msg, |
||
| 196 | 'attachment_type' => 0, |
||
| 197 | ) |
||
| 198 | ); |
||
| 199 | list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request); |
||
| 200 | $smcFunc['db_free_result']($request); |
||
| 201 | } |
||
| 202 | |||
| 203 | else |
||
| 204 | $context['attachments'] = array( |
||
| 205 | 'quantity' => 0, |
||
| 206 | 'total_size' => 0, |
||
| 207 | ); |
||
| 208 | |||
| 209 | // Check for other general errors here. |
||
| 210 | |||
| 211 | // If we have an initial error, delete the files. |
||
| 212 | if (!empty($this->_generalErrors)) |
||
| 213 | { |
||
| 214 | // And delete the files 'cos they ain't going nowhere. |
||
| 215 | foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) |
||
| 216 | if (file_exists($_FILES['attachment']['tmp_name'][$n])) |
||
| 217 | unlink($_FILES['attachment']['tmp_name'][$n]); |
||
| 218 | |||
| 219 | $_FILES['attachment']['tmp_name'] = array(); |
||
| 220 | |||
| 221 | // No point in going further with this. |
||
| 222 | return; |
||
| 223 | } |
||
| 224 | |||
| 225 | // Loop through $_FILES['attachment'] array and move each file to the current attachments folder. |
||
| 226 | foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) |
||
| 227 | { |
||
| 228 | if ($_FILES['attachment']['name'][$n] == '') |
||
| 229 | continue; |
||
| 230 | |||
| 231 | // First, let's first check for PHP upload errors. |
||
| 232 | $errors = array(); |
||
| 233 | if (!empty($_FILES['attachment']['error'][$n])) |
||
| 234 | { |
||
| 235 | if ($_FILES['attachment']['error'][$n] == 2) |
||
| 236 | $errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit'])); |
||
| 237 | |||
| 238 | else |
||
| 239 | log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]); |
||
| 240 | |||
| 241 | // Log this one, because... |
||
| 242 | if ($_FILES['attachment']['error'][$n] == 6) |
||
| 243 | log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical'); |
||
| 244 | |||
| 245 | // Weird, no errors were cached, still fill out a generic one. |
||
| 246 | if (empty($errors)) |
||
| 247 | $errors[] = 'attach_php_error'; |
||
| 248 | } |
||
| 249 | |||
| 250 | // Try to move and rename the file before doing any more checks on it. |
||
| 251 | $attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()); |
||
| 252 | $destName = $this->_attchDir . '/' . $attachID; |
||
| 253 | |||
| 254 | // No errors, YAY! |
||
| 255 | if (empty($errors)) |
||
| 256 | { |
||
| 257 | // The reported MIME type of the attachment might not be reliable. |
||
| 258 | $detected_mime_type = get_mime_type($_FILES['attachment']['tmp_name'][$n], true); |
||
| 259 | if ($detected_mime_type !== false) |
||
| 260 | $_FILES['attachment']['type'][$n] = $detected_mime_type; |
||
| 261 | |||
| 262 | $_SESSION['temp_attachments'][$attachID] = array( |
||
| 263 | 'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])), |
||
| 264 | 'tmp_name' => $destName, |
||
| 265 | 'size' => $_FILES['attachment']['size'][$n], |
||
| 266 | 'type' => $_FILES['attachment']['type'][$n], |
||
| 267 | 'id_folder' => $modSettings['currentAttachmentUploadDir'], |
||
| 268 | 'errors' => array(), |
||
| 269 | ); |
||
| 270 | |||
| 271 | // Move the file to the attachments folder with a temp name for now. |
||
| 272 | if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName)) |
||
| 273 | smf_chmod($destName, 0644); |
||
| 274 | |||
| 275 | // This is madness!! |
||
| 276 | else |
||
| 277 | { |
||
| 278 | // File couldn't be moved. |
||
| 279 | $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout'; |
||
| 280 | if (file_exists($_FILES['attachment']['tmp_name'][$n])) |
||
| 281 | unlink($_FILES['attachment']['tmp_name'][$n]); |
||
| 282 | } |
||
| 283 | } |
||
| 284 | |||
| 285 | // Fill up a nice array with some data from the file and the errors encountered so far. |
||
| 286 | else |
||
| 287 | { |
||
| 288 | $_SESSION['temp_attachments'][$attachID] = array( |
||
| 289 | 'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])), |
||
| 290 | 'tmp_name' => $destName, |
||
| 291 | 'errors' => $errors, |
||
| 292 | ); |
||
| 293 | |||
| 294 | if (file_exists($_FILES['attachment']['tmp_name'][$n])) |
||
| 295 | unlink($_FILES['attachment']['tmp_name'][$n]); |
||
| 296 | } |
||
| 297 | |||
| 298 | // If there's no errors to this point. We still do need to apply some additional checks before we are finished. |
||
| 299 | if (empty($_SESSION['temp_attachments'][$attachID]['errors'])) |
||
| 300 | attachmentChecks($attachID); |
||
| 301 | } |
||
| 302 | |||
| 303 | // Mod authors, finally a hook to hang an alternate attachment upload system upon |
||
| 304 | // Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()) |
||
| 305 | // Populate $_SESSION['temp_attachments'][$attachID] with the following: |
||
| 306 | // name => The file name |
||
| 307 | // tmp_name => Path to the temp file ($this->_attchDir . '/' . $attachID). |
||
| 308 | // size => File size (required). |
||
| 309 | // type => MIME type (optional if not available on upload). |
||
| 310 | // id_folder => $modSettings['currentAttachmentUploadDir'] |
||
| 311 | // errors => An array of errors (use the index of the $txt variable for that error). |
||
| 312 | // Template changes can be done using "integrate_upload_template". |
||
| 313 | call_integration_hook('integrate_attachment_upload', array()); |
||
| 314 | } |
||
| 315 | |||
| 316 | protected function createAttach() |
||
| 317 | { |
||
| 318 | global $txt, $user_info, $modSettings; |
||
| 319 | |||
| 320 | // Create an empty session var to keep track of all the files we attached. |
||
| 321 | if (!isset($_SESSION['already_attached'])) |
||
| 322 | $_SESSION['already_attached'] = array(); |
||
| 323 | |||
| 324 | foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) |
||
| 325 | { |
||
| 326 | $attachmentOptions = array( |
||
| 327 | 'post' => $this->_msg, |
||
| 328 | 'poster' => $user_info['id'], |
||
| 329 | 'name' => $attachment['name'], |
||
| 330 | 'tmp_name' => $attachment['tmp_name'], |
||
| 331 | 'size' => isset($attachment['size']) ? $attachment['size'] : 0, |
||
| 332 | 'mime_type' => isset($attachment['type']) ? $attachment['type'] : '', |
||
| 333 | 'id_folder' => isset($attachment['id_folder']) ? $attachment['id_folder'] : $modSettings['currentAttachmentUploadDir'], |
||
| 334 | 'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'), |
||
| 335 | 'errors' => array(), |
||
| 336 | ); |
||
| 337 | |||
| 338 | if (empty($attachment['errors'])) |
||
| 339 | { |
||
| 340 | if (createAttachment($attachmentOptions)) |
||
| 341 | { |
||
| 342 | // Avoid JS getting confused. |
||
| 343 | $attachmentOptions['attachID'] = $attachmentOptions['id']; |
||
| 344 | unset($attachmentOptions['id']); |
||
| 345 | |||
| 346 | $_SESSION['already_attached'][$attachmentOptions['attachID']] = $attachmentOptions['attachID']; |
||
| 347 | |||
| 348 | if (!empty($attachmentOptions['thumb'])) |
||
| 349 | $_SESSION['already_attached'][$attachmentOptions['thumb']] = $attachmentOptions['thumb']; |
||
| 350 | |||
| 351 | if ($this->_msg) |
||
| 352 | assignAttachments($_SESSION['already_attached'], $this->_msg); |
||
| 353 | } |
||
| 354 | } |
||
| 355 | else |
||
| 356 | { |
||
| 357 | // Sort out the errors for display and delete any associated files. |
||
| 358 | $log_these = array('attachments_no_create', 'attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file'); |
||
| 359 | |||
| 360 | foreach ($attachment['errors'] as $error) |
||
| 361 | { |
||
| 362 | $attachmentOptions['errors'][] = vsprintf($txt['attach_warning'], $attachment['name']); |
||
| 363 | |||
| 364 | if (!is_array($error)) |
||
| 365 | { |
||
| 366 | $attachmentOptions['errors'][] = $txt[$error]; |
||
| 367 | if (in_array($error, $log_these)) |
||
| 368 | log_error($attachment['name'] . ': ' . $txt[$error], 'critical'); |
||
| 369 | } |
||
| 370 | else |
||
| 371 | $attachmentOptions['errors'][] = vsprintf($txt[$error[0]], $error[1]); |
||
| 372 | } |
||
| 373 | if (file_exists($attachment['tmp_name'])) |
||
| 374 | unlink($attachment['tmp_name']); |
||
| 375 | } |
||
| 376 | |||
| 377 | // You don't need to know. |
||
| 378 | unset($attachmentOptions['tmp_name']); |
||
| 379 | unset($attachmentOptions['destination']); |
||
| 380 | |||
| 381 | // Regardless of errors, pass the results. |
||
| 382 | $this->_attachResults[] = $attachmentOptions; |
||
| 383 | } |
||
| 384 | |||
| 385 | // Temp save this on the db. |
||
| 386 | if (!empty($_SESSION['already_attached'])) |
||
| 387 | $this->_attachSuccess = $_SESSION['already_attached']; |
||
| 388 | |||
| 389 | unset($_SESSION['temp_attachments']); |
||
| 390 | } |
||
| 391 | |||
| 392 | protected function setResponse($data = array()) |
||
| 393 | { |
||
| 394 | global $txt; |
||
| 395 | |||
| 396 | // Some default values in case something is missed or neglected :P |
||
| 397 | $this->_response = array( |
||
| 398 | 'text' => 'attach_php_error', |
||
| 399 | 'type' => 'error', |
||
| 400 | 'data' => false, |
||
| 401 | ); |
||
| 402 | |||
| 403 | // Adding needs some VIP treatment. |
||
| 404 | if ($this->_sa == 'add') |
||
| 405 | { |
||
| 406 | // Is there any generic errors? made some sense out of them! |
||
| 407 | if ($this->_generalErrors) |
||
| 408 | foreach ($this->_generalErrors as $k => $v) |
||
| 409 | $this->_generalErrors[$k] = (is_array($v) ? vsprintf($txt[$v[0]], $v[1]) : $txt[$v]); |
||
| 410 | |||
| 411 | // Gotta urlencode the filename. |
||
| 412 | if ($this->_attachResults) |
||
| 413 | foreach ($this->_attachResults as $k => $v) |
||
| 414 | $this->_attachResults[$k]['name'] = urlencode($this->_attachResults[$k]['name']); |
||
| 415 | |||
| 416 | $this->_response = array( |
||
| 417 | 'files' => $this->_attachResults ? $this->_attachResults : false, |
||
| 418 | 'generalErrors' => $this->_generalErrors ? $this->_generalErrors : false, |
||
| 419 | ); |
||
| 420 | } |
||
| 421 | |||
| 422 | // Rest of us mere mortals gets no special treatment... |
||
| 423 | elseif (!empty($data)) |
||
| 424 | if (!empty($data['text']) && !empty($txt[$data['text']])) |
||
| 425 | $this->_response['text'] = $txt[$data['text']]; |
||
| 426 | } |
||
| 427 | |||
| 428 | protected function sendResponse() |
||
| 429 | { |
||
| 430 | global $smcFunc, $modSettings, $context; |
||
| 431 | |||
| 432 | ob_end_clean(); |
||
| 433 | |||
| 434 | if (!empty($modSettings['CompressedOutput'])) |
||
| 435 | @ob_start('ob_gzhandler'); |
||
| 436 | |||
| 437 | else |
||
| 438 | ob_start(); |
||
| 439 | |||
| 440 | // Set the header. |
||
| 441 | header('content-type: application/json; charset=' . $context['character_set'] . ''); |
||
| 442 | |||
| 443 | echo $smcFunc['json_encode']($this->_response ? $this->_response : array()); |
||
| 444 | |||
| 445 | // Done. |
||
| 446 | obExit(false); |
||
| 447 | die; |
||
|
0 ignored issues
–
show
|
|||
| 448 | } |
||
| 449 | } |
||
| 450 | |||
| 451 | ?> |
In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.