/** * Handles the actual saving of attachments to a directory. * * What it does: * - Loops through $_FILES['attachment'] array and saves each file to the current attachments folder. * - Validates the save location actually exists. * * @package Attachments * @param int|null $id_msg = null or id of the message with attachments, if any. * If null, this is an upload in progress for a new post. */ function processAttachments($id_msg = null) { global $context, $modSettings, $txt, $user_info, $ignore_temp, $topic, $board; $attach_errors = Attachment_Error_Context::context(); // Make sure we're uploading to the right place. if (!empty($modSettings['automanage_attachments'])) { automanage_attachments_check_directory(); } if (!is_array($modSettings['attachmentUploadDir'])) { $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); } $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; // Is the attachments folder actualy there? if (!empty($context['dir_creation_error'])) { $initial_error = $context['dir_creation_error']; } elseif (!is_dir($context['attach_dir'])) { $initial_error = 'attach_folder_warning'; log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical'); } if (!isset($initial_error) && !isset($context['attachments']['quantity'])) { // If this isn't a new post, check the current attachments. if (!empty($id_msg)) { list($context['attachments']['quantity'], $context['attachments']['total_size']) = attachmentsSizeForMessage($id_msg); } else { $context['attachments']['quantity'] = 0; $context['attachments']['total_size'] = 0; } } // Hmm. There are still files in session. $ignore_temp = false; if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1) { // Let's try to keep them. But... $ignore_temp = true; // If new files are being added. We can't ignore those foreach ($_FILES['attachment']['tmp_name'] as $dummy) { if (!empty($dummy)) { $ignore_temp = false; break; } } // Need to make space for the new files. So, bye bye. if (!$ignore_temp) { foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) { if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false) { @unlink($attachment['tmp_name']); } } $attach_errors->activate()->addError('temp_attachments_flushed'); $_SESSION['temp_attachments'] = array(); } } if (!isset($_FILES['attachment']['name'])) { $_FILES['attachment']['tmp_name'] = array(); } if (!isset($_SESSION['temp_attachments'])) { $_SESSION['temp_attachments'] = array(); } // Remember where we are at. If it's anywhere at all. if (!$ignore_temp) { $_SESSION['temp_attachments']['post'] = array('msg' => !empty($id_msg) ? $id_msg : 0, 'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0, 'topic' => !empty($topic) ? $topic : 0, 'board' => !empty($board) ? $board : 0); } // If we have an initial error, lets just display it. if (!empty($initial_error)) { $_SESSION['temp_attachments']['initial_error'] = $initial_error; // This is a generic error $attach_errors->activate(); $attach_errors->addError('attach_no_upload'); $attach_errors->addError(is_array($attachment) ? array($attachment[0], $attachment[1]) : $attachment); // And delete the files 'cos they ain't going nowhere. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) { if (file_exists($_FILES['attachment']['tmp_name'][$n])) { unlink($_FILES['attachment']['tmp_name'][$n]); } } $_FILES['attachment']['tmp_name'] = array(); } // Loop through $_FILES['attachment'] array and move each file to the current attachments folder. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) { if ($_FILES['attachment']['name'][$n] == '') { continue; } // First, let's first check for PHP upload errors. $errors = attachmentUploadChecks($n); // Set the names and destination for this file $attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()); $destName = $context['attach_dir'] . '/' . $attachID; // If we are error free, Try to move and rename the file before doing more checks on it. if (empty($errors)) { $_SESSION['temp_attachments'][$attachID] = array('name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n]), ENT_COMPAT, 'UTF-8'), 'tmp_name' => $destName, 'attachid' => $attachID, 'size' => $_FILES['attachment']['size'][$n], 'type' => $_FILES['attachment']['type'][$n], 'id_folder' => $modSettings['currentAttachmentUploadDir'], 'errors' => array()); // Move the file to the attachments folder with a temp name for now. if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName)) { @chmod($destName, 0644); } else { $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout'; if (file_exists($_FILES['attachment']['tmp_name'][$n])) { unlink($_FILES['attachment']['tmp_name'][$n]); } } } else { $_SESSION['temp_attachments'][$attachID] = array('name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n]), ENT_COMPAT, 'UTF-8'), 'tmp_name' => $destName, 'errors' => $errors); if (file_exists($_FILES['attachment']['tmp_name'][$n])) { unlink($_FILES['attachment']['tmp_name'][$n]); } } // If there were no errors to this pont, we apply some addtional checks if (empty($_SESSION['temp_attachments'][$attachID]['errors'])) { attachmentChecks($attachID); } // Sort out the errors for display and delete any associated files. if (!empty($_SESSION['temp_attachments'][$attachID]['errors'])) { $attach_errors->addAttach($attachID, $_SESSION['temp_attachments'][$attachID]['name']); $log_these = array('attachments_no_create', 'attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file'); foreach ($_SESSION['temp_attachments'][$attachID]['errors'] as $error) { if (!is_array($error)) { $attach_errors->addError($error); if (in_array($error, $log_these)) { log_error($_SESSION['temp_attachments'][$attachID]['name'] . ': ' . $txt[$error], 'critical'); } } else { $attach_errors->addError(array($error[0], $error[1])); } } } } // Mod authors, finally a hook to hang an alternate attachment upload system upon // Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()) // Populate $_SESSION['temp_attachments'][$attachID] with the following: // name => The file name // tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID). // size => File size (required). // type => MIME type (optional if not available on upload). // id_folder => $modSettings['currentAttachmentUploadDir'] // errors => An array of errors (use the index of the $txt variable for that error). // Template changes can be done using "integrate_upload_template". call_integration_hook('integrate_attachment_upload'); }
/** * Maintenance function to move attachments from one directory to another */ public function action_transfer() { global $modSettings, $txt; checkSession(); // We will need the functions from here require_once SUBSDIR . '/Attachments.subs.php'; require_once SUBSDIR . '/ManageAttachments.subs.php'; // The list(s) of directory's that are available. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); if (!empty($modSettings['attachment_basedirectories'])) { $modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']); } else { $modSettings['basedirectory_for_attachments'] = array(); } // Clean the inputs $_POST['from'] = (int) $_POST['from']; $_POST['auto'] = !empty($_POST['auto']) ? (int) $_POST['auto'] : 0; $_POST['to'] = (int) $_POST['to']; $start = !empty($_POST['empty_it']) ? 0 : $modSettings['attachmentDirFileLimit']; $_SESSION['checked'] = !empty($_POST['empty_it']) ? true : false; // Prepare for the moving $limit = 501; $results = array(); $dir_files = 0; $current_progress = 0; $total_moved = 0; $total_not_moved = 0; // Need to know where we are moving things from if (empty($_POST['from']) || empty($_POST['auto']) && empty($_POST['to'])) { $results[] = $txt['attachment_transfer_no_dir']; } // Same location, that's easy if ($_POST['from'] == $_POST['to']) { $results[] = $txt['attachment_transfer_same_dir']; } // No errors so determine how many we may have to move if (empty($results)) { // Get the total file count for the progress bar. $total_progress = getFolderAttachmentCount($_POST['from']); $total_progress -= $start; if ($total_progress < 1) { $results[] = $txt['attachment_transfer_no_find']; } } // Nothing to move (no files in source or below the max limit) if (empty($results)) { // Moving them automaticaly? if (!empty($_POST['auto'])) { $modSettings['automanage_attachments'] = 1; // Create sub directroys off the root or from an attachment directory? $modSettings['use_subdirectories_for_attachments'] = $_POST['auto'] == -1 ? 0 : 1; $modSettings['basedirectory_for_attachments'] = $_POST['auto'] > 0 ? $modSettings['attachmentUploadDir'][$_POST['auto']] : $modSettings['basedirectory_for_attachments']; // Finaly, where do they need to go automanage_attachments_check_directory(); $new_dir = $modSettings['currentAttachmentUploadDir']; } else { $new_dir = $_POST['to']; } $modSettings['currentAttachmentUploadDir'] = $new_dir; $break = false; while ($break === false) { @set_time_limit(300); if (function_exists('apache_reset_timeout')) { @apache_reset_timeout(); } // If limits are set, get the file count and size for the destination folder if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))) { $current_dir = attachDirProperties($new_dir); $dir_files = $current_dir['files']; $dir_size = $current_dir['size']; } // Find some attachments to move list($tomove_count, $tomove) = findAttachmentsToMove($_POST['from'], $start, $limit); // Nothing found to move if ($tomove_count === 0) { if (empty($current_progress)) { $results[] = $txt['attachment_transfer_no_find']; } break; } // No more to move after this batch then set the finished flag. if ($tomove_count < $limit) { $break = true; } // Move them $moved = array(); foreach ($tomove as $row) { $source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); $dest = $modSettings['attachmentUploadDir'][$new_dir] . '/' . basename($source); // Size and file count check if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit'])) { $dir_files++; $dir_size += !empty($row['size']) ? $row['size'] : filesize($source); // If we've reached a directory limit. Do something if we are in auto mode, otherwise set an error. if (!empty($modSettings['attachmentDirSizeLimit']) && $dir_size > $modSettings['attachmentDirSizeLimit'] * 1024 || !empty($modSettings['attachmentDirFileLimit']) && $dir_files > $modSettings['attachmentDirFileLimit']) { // Since we're in auto mode. Create a new folder and reset the counters. if (!empty($_POST['auto'])) { automanage_attachments_by_space(); $results[] = sprintf($txt['attachments_transfered'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]); if (!empty($total_not_moved)) { $results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved); } $dir_files = 0; $total_moved = 0; $total_not_moved = 0; $break = false; break; } else { $results[] = $txt['attachment_transfer_no_room']; $break = true; break; } } } // Actually move the file if (@rename($source, $dest)) { $total_moved++; $current_progress++; $moved[] = $row['id_attach']; } else { $total_not_moved++; } } // Update the database to reflect the new file location if (!empty($moved)) { moveAttachments($moved, $new_dir); } $new_dir = $modSettings['currentAttachmentUploadDir']; // Create / update the progress bar. // @todo why was this done this way? if (!$break) { $percent_done = min(round($current_progress / $total_progress * 100, 0), 100); $prog_bar = ' <div class="progress_bar"> <div class="full_bar">' . $percent_done . '%</div> <div class="green_percent" style="width: ' . $percent_done . '%;"> </div> </div>'; // Write it to a file so it can be displayed $fp = fopen(BOARDDIR . '/progress.php', 'w'); fwrite($fp, $prog_bar); fclose($fp); usleep(500000); } } $results[] = sprintf($txt['attachments_transfered'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]); if (!empty($total_not_moved)) { $results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved); } } // All done, time to clean up $_SESSION['results'] = $results; if (file_exists(BOARDDIR . '/progress.php')) { unlink(BOARDDIR . '/progress.php'); } redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer'); }
function processAttachments() { global $context, $modSettings, $smcFunc, $txt, $user_info; // Make sure we're uploading to the right place. if (!empty($modSettings['automanage_attachments'])) { automanage_attachments_check_directory(); } if (!is_array($modSettings['attachmentUploadDir'])) { $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); } $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; // Is the attachments folder actualy there? if (!empty($context['dir_creation_error'])) { $initial_error = $context['dir_creation_error']; } elseif (!is_dir($context['attach_dir'])) { $initial_error = 'attach_folder_warning'; log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical'); } if (!isset($initial_error) && !isset($context['attachments'])) { // If this isn't a new post, check the current attachments. if (isset($_REQUEST['msg'])) { $request = $smcFunc['db_query']('', ' SELECT COUNT(*), SUM(size) FROM {db_prefix}attachments WHERE id_msg = {int:id_msg} AND attachment_type = {int:attachment_type}', array('id_msg' => (int) $_REQUEST['msg'], 'attachment_type' => 0)); list($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); } else { $context['attachments'] = array('quantity' => 0, 'total_size' => 0); } } // Hmm. There are still files in session. $ignore_temp = false; if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1) { // Let's try to keep them. But... $ignore_temp = true; // If new files are being added. We can't ignore those foreach ($_FILES['attachment']['tmp_name'] as $dummy) { if (!empty($dummy)) { $ignore_temp = false; break; } } // Need to make space for the new files. So, bye bye. if (!$ignore_temp) { foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) { if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false) { unlink($attachment['tmp_name']); } } $context['we_are_history'] = $txt['error_temp_attachments_flushed']; $_SESSION['temp_attachments'] = array(); } } if (!isset($_FILES['attachment']['name'])) { $_FILES['attachment']['tmp_name'] = array(); } if (!isset($_SESSION['temp_attachments'])) { $_SESSION['temp_attachments'] = array(); } // Remember where we are at. If it's anywhere at all. if (!$ignore_temp) { $_SESSION['temp_attachments']['post'] = array('msg' => !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0, 'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0, 'topic' => !empty($topic) ? $topic : 0, 'board' => !empty($board) ? $board : 0); } // If we have an itital error, lets just display it. if (!empty($initial_error)) { $_SESSION['temp_attachments']['initial_error'] = $initial_error; // And delete the files 'cos they ain't going nowhere. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) { if (file_exists($_FILES['attachment']['tmp_name'][$n])) { unlink($_FILES['attachment']['tmp_name'][$n]); } } $_FILES['attachment']['tmp_name'] = array(); } // Loop through $_FILES['attachment'] array and move each file to the current attachments folder. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy) { if ($_FILES['attachment']['name'][$n] == '') { continue; } // First, let's first check for PHP upload errors. $errors = array(); if (!empty($_FILES['attachment']['error'][$n])) { if ($_FILES['attachment']['error'][$n] == 2) { $errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit'])); } elseif ($_FILES['attachment']['error'][$n] == 6) { log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical'); } else { log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]); } if (empty($errors)) { $errors[] = 'attach_php_error'; } } // Try to move and rename the file before doing any more checks on it. $attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()); $destName = $context['attach_dir'] . '/' . $attachID; if (empty($errors)) { $_SESSION['temp_attachments'][$attachID] = array('name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])), 'tmp_name' => $destName, 'size' => $_FILES['attachment']['size'][$n], 'type' => $_FILES['attachment']['type'][$n], 'id_folder' => $modSettings['currentAttachmentUploadDir'], 'errors' => array()); // Move the file to the attachments folder with a temp name for now. if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName)) { @chmod($destName, 0644); } else { $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout'; if (file_exists($_FILES['attachment']['tmp_name'][$n])) { unlink($_FILES['attachment']['tmp_name'][$n]); } } } else { $_SESSION['temp_attachments'][$attachID] = array('name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])), 'tmp_name' => $destName, 'errors' => $errors); if (file_exists($_FILES['attachment']['tmp_name'][$n])) { unlink($_FILES['attachment']['tmp_name'][$n]); } } // If there's no errors to this pont. We still do need to apply some addtional checks before we are finished. if (empty($_SESSION['temp_attachments'][$attachID]['errors'])) { attachmentChecks($attachID); } } // Mod authors, finally a hook to hang an alternate attachment upload system upon // Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()) // Populate $_SESSION['temp_attachments'][$attachID] with the following: // name => The file name // tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID). // size => File size (required). // type => MIME type (optional if not available on upload). // id_folder => $modSettings['currentAttachmentUploadDir'] // errors => An array of errors (use the index of the $txt variable for that error). // Template changes can be done using "integrate_upload_template". call_integration_hook('integrate_attachment_upload', array()); }