浏览代码

! Attachment management overhaul from Kays hard work. Adds much improved error management of attachments and overall program flow.

Spuds 12 年之前
父节点
当前提交
a8f52e3a12

+ 459 - 272
Sources/Post.php

@@ -19,12 +19,12 @@ if (!defined('SMF'))
 
 /**
  * handles showing the post screen, loading the post to be modified, and loading any post quoted.
- * additionally handles previews of posts.
- * @uses the Post template and language file, main sub template.
- * allows wireless access using the protocol_post sub template.
- * requires different permissions depending on the actions, but most notably post_new, post_reply_own, and post_reply_any.
- * shows options for the editing and posting of calendar events and attachments, as well as the posting of polls.
- * accessed from ?action=post.
+ * - additionally handles previews of posts.
+ * - @uses the Post template and language file, main sub template.
+ * - allows wireless access using the protocol_post sub template.
+ * - requires different permissions depending on the actions, but most notably post_new, post_reply_own, and post_reply_any.
+ * - shows options for the editing and posting of calendar events and attachments, as well as the posting of polls.
+ * - accessed from ?action=post.
  */
 function Post()
 {
@@ -48,6 +48,18 @@ function Post()
 
 	require_once($sourcedir . '/Subs-Post.php');
 
+	// Any files to include for post?
+	if (!isset($post_includes) && !empty($modSettings['integrate_post_include']))
+	{
+		$post_includes = explode(',', $modSettings['integrate_post_include']);
+		foreach ($post_includes as $include)
+		{
+			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
+			if (file_exists($include))
+				require_once($include);
+		}
+	}
+
 	if (isset($_REQUEST['xml']))
 	{
 		$context['sub_template'] = 'post';
@@ -625,6 +637,7 @@ function Post()
 						continue;
 					$context['current_attachments'][] = array(
 						'name' => htmlspecialchars($row['filename']),
+						'size' => $row['filesize'],
 						'id' => $row['id_attach'],
 						'approved' => $row['approved'],
 					);
@@ -734,6 +747,7 @@ function Post()
 			if ($attachment['filesize'] >= 0 && !empty($modSettings['attachmentEnable']))
 				$context['current_attachments'][] = array(
 					'name' => htmlspecialchars($attachment['filename']),
+					'size' => $attachment['filesize'],
 					'id' => $attachment['id_attach'],
 					'approved' => $attachment['attachment_approved'],
 				);
@@ -840,14 +854,13 @@ function Post()
 		}
 	}
 
-	/**
-	 * This won't work if you're posting an event.
-	 */
-	if (allowedTo('post_attachment') || allowedTo('post_unapproved_attachments'))
+	$context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments')));
+	if ($context['can_post_attachment'])
 	{
-		if (empty($_SESSION['temp_attachments']))
-			$_SESSION['temp_attachments'] = array();
-
+		// If there are attachments, calculate the total size and how many.
+		$context['attachments']['total_size'] = 0;
+		$context['attachments']['quantity'] = 0;
+		if (!empty($context['current_attachments']))
 		if (!empty($modSettings['currentAttachmentUploadDir']))
 		{
 			if (!is_array($modSettings['attachmentUploadDir']))
@@ -862,151 +875,144 @@ function Post()
 		// 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 ($quantity, $total_size) = $smcFunc['db_fetch_row']($request);
-			$smcFunc['db_free_result']($request);
-		}
-		else
-		{
-			$quantity = 0;
-			$total_size = 0;
+			$context['attachments']['quantity'] = count($context['current_attachments']);
+			foreach ($context['current_attachments'] as $attachment)
+				$context['attachments']['total_size'] += $attachment['size'];
 		}
 
-		$temp_start = 0;
+		// A bit of house keeping first.
+		if (!empty($_SESSION['temp_attachments']) && count($_SESSION['temp_attachments']) == 1)
+			unset($_SESSION['temp_attachments']);
 
 		if (!empty($_SESSION['temp_attachments']))
 		{
-			if ($context['current_action'] != 'post2' || !empty($_POST['from_qr']))
+			// Is this a request to delete them?
+			if (isset($_GET['delete_temp']))
 			{
-				$context['post_error']['messages'][] = $txt['error_temp_attachments'];
-				$context['error_type'] = 'minor';
+				foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
+				{
+					if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false)
+						if (file_exists($attachment['tmp_name']))
+							unlink($attachment['tmp_name']);
+				}
+				$context['post_error']['messages'][] = $txt['error_temp_attachments_gone'];
+				$_SESSION['temp_attachments'] = array();
 			}
-
-			foreach ($_SESSION['temp_attachments'] as $attachID => $name)
+			// Hmm, coming in fresh and there are files in session.
+			elseif ($context['current_action'] != 'post2' || !empty($_POST['from_qr']))
 			{
-				$temp_start++;
-
-				if (preg_match('~^post_tmp_' . $user_info['id'] . '_\d+$~', $attachID) == 0)
+				// Let's be nice and see if they belong here first.
+				if ((empty($_REQUEST['msg']) && empty($_SESSION['temp_attachments']['post']['msg']) && $_SESSION['temp_attachments']['post']['board'] == $board) || (!empty($_REQUEST['msg']) && $_SESSION['temp_attachments']['post']['msg'] == $_REQUEST['msg']))
 				{
-					unset($_SESSION['temp_attachments'][$attachID]);
-					continue;
-				}
+					// See if any files still exist before showing the warning message and the files attached.
+					foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
+					{
+						if (strpos($attachID, 'post_tmp_' . $user_info['id']) === false)
+							continue;
 
-				if (!empty($_POST['attach_del']) && !in_array($attachID, $_POST['attach_del']))
-				{
-					$deleted_attachments = true;
-					unset($_SESSION['temp_attachments'][$attachID]);
-					@unlink($current_attach_dir . '/' . $attachID);
-					continue;
+						if (file_exists($attachment['tmp_name']))
+						{
+							$context['post_error']['messages'][] = $txt['error_temp_attachments_new'];
+							$context['files_in_session_warning'] = $txt['attached_files_in_session'];
+							unset($_SESSION['temp_attachments']['post']['files']);
+							break;
+						}
+					}
 				}
+				else
+				{
+					// Since, they don't belong here. Let's inform the user that they exist..
+					if (!empty($topic))
+						$delete_link = '<a href="' . $scripturl . '?action=post' .(!empty($_REQUEST['msg']) ? (';msg=' . $_REQUEST['msg']) : '') . (!empty($_REQUEST['last_msg']) ? (';last_msg=' . $_REQUEST['last_msg']) : '') . ';topic=' . $topic . ';delete_temp">' . $txt['here'] . '</a>';
+					else
+						$delete_link = '<a href="' . $scripturl . '?action=post;board=' . $board . ';delete_temp">' . $txt['here'] . '</a>';
+
+					// Compile a list of the files to show the user.
+					$file_list = array();
+					foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
+						if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false)
+							$file_list[] =  $attachment['name'];
+
+					$_SESSION['temp_attachments']['post']['files'] = $file_list;
+					$file_list = '<div class="attachments">' . implode('<br />', $file_list) . '</div>';
+
+					if (!empty($_SESSION['temp_attachments']['post']['msg']))
+					{
+						// We have a message id, so we can link back to the old topic they were trying to edit..
+						$goback_link = '<a href="' . $scripturl . '?action=post' .(!empty($_SESSION['temp_attachments']['post']['msg']) ? (';msg=' . $_SESSION['temp_attachments']['post']['msg']) : '') . (!empty($_SESSION['temp_attachments']['post']['last_msg']) ? (';last_msg=' . $_SESSION['temp_attachments']['post']['last_msg']) : '') . ';topic=' . $_SESSION['temp_attachments']['post']['topic'] . ';additionalOptions">' . $txt['here'] . '</a>';
 
-				$quantity++;
-				$total_size += filesize($current_attach_dir . '/' . $attachID);
-
-				$context['current_attachments'][] = array(
-					'name' => htmlspecialchars($name),
-					'id' => $attachID,
-					'approved' => 1,
-				);
+						$context['post_error']['messages'][] = vsprintf($txt['error_temp_attachments_found'], array($delete_link, $goback_link, $file_list));
+						$context['ignore_temp_attachments'] = true;
+					}
+					else
+					{
+						$context['post_error']['messages'][] = vsprintf($txt['error_temp_attachments_lost'], array($delete_link, $file_list));
+						$context['ignore_temp_attachments'] = true;
+					}
+				}
 			}
-		}
-
-		if (!empty($_POST['attach_del']))
-		{
-			$del_temp = array();
-			foreach ($_POST['attach_del'] as $i => $dummy)
-				$del_temp[$i] = (int) $dummy;
 
-			foreach ($context['current_attachments'] as $k => $dummy)
-				if (!in_array($dummy['id'], $del_temp))
-				{
-					$context['current_attachments'][$k]['unchecked'] = true;
-					$deleted_attachments = !isset($deleted_attachments) || is_bool($deleted_attachments) ? 1 : $deleted_attachments + 1;
-					$quantity--;
-				}
-		}
+			if (!empty($context['we_are_history']))
+				$context['post_error']['messages'][] = '<br />' . $context['we_are_history'];
 
-		if (!empty($_FILES['attachment']))
-			foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
+			foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
 			{
-				if ($_FILES['attachment']['name'][$n] == '')
-					continue;
+				if (isset($context['ignore_temp_attachments']) || isset($_SESSION['temp_attachments']['post']['files']))
+					break;
 
-				if (!is_uploaded_file($_FILES['attachment']['tmp_name'][$n]) || (ini_get('open_basedir') == '' && !file_exists($_FILES['attachment']['tmp_name'][$n])))
-					fatal_lang_error('attach_timeout', 'critical');
-
-				if (!empty($modSettings['attachmentSizeLimit']) && $_FILES['attachment']['size'][$n] > $modSettings['attachmentSizeLimit'] * 1024)
-					fatal_lang_error('file_too_big', false, array($modSettings['attachmentSizeLimit']));
-
-				$quantity++;
-				if (!empty($modSettings['attachmentNumPerPostLimit']) && $quantity > $modSettings['attachmentNumPerPostLimit'])
-					fatal_lang_error('attachments_limit_per_post', false, array($modSettings['attachmentNumPerPostLimit']));
-
-				$total_size += $_FILES['attachment']['size'][$n];
-				if (!empty($modSettings['attachmentPostLimit']) && $total_size > $modSettings['attachmentPostLimit'] * 1024)
-					fatal_lang_error('file_too_big', false, array($modSettings['attachmentPostLimit']));
+				if ($attachID != 'initial_error' && strpos($attachID, 'post_tmp_' . $user_info['id']) === false)
+					continue;
 
-				if (!empty($modSettings['attachmentCheckExtensions']))
+				if ($attachID == 'initial_error')
 				{
-					if (!in_array(strtolower(substr(strrchr($_FILES['attachment']['name'][$n], '.'), 1)), explode(',', strtolower($modSettings['attachmentExtensions']))))
-						fatal_error($_FILES['attachment']['name'][$n] . '.<br />' . $txt['cant_upload_type'] . ' ' . $modSettings['attachmentExtensions'] . '.', false);
+					$context['post_error']['messages'][] = '<br />' . $txt['attach_no_upload'] . '<div style="padding: 0 1em;">' . (is_array($attachment) ? vsprintf($txt[$attachment[0]], $attachment[1]) : $txt[$attachment]) . '</div>';
+					unset($_SESSION['temp_attachments']);
+					break;
 				}
 
-				if (!empty($modSettings['attachmentDirSizeLimit']))
+				// Show any errors which might of occured.
+				if (!empty($attachment['errors']))
 				{
-					// Make sure the directory isn't full.
-					$dirSize = 0;
-					$dir = @opendir($current_attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical');
-					while ($file = readdir($dir))
-					{
-						if ($file == '.' || $file == '..')
-							continue;
-
-						if (preg_match('~^post_tmp_\d+_\d+$~', $file) != 0)
-						{
-							// Temp file is more than 5 hours old!
-							if (filemtime($current_attach_dir . '/' . $file) < time() - 18000)
-								@unlink($current_attach_dir . '/' . $file);
-							continue;
-						}
-
-						$dirSize += filesize($current_attach_dir . '/' . $file);
-					}
-					closedir($dir);
+					$errors = empty($errors) ? '<br />' : '';
+					$errors .= vsprintf($txt['attach_warning'], $attachment['name']) . '<div style="padding: 0 1em;">';
+					foreach($attachment['errors'] as $error)
+						$errors .= (is_array($error) ? vsprintf($txt[$error[0]], $error[1]) : $txt[$error]) . '<br  />';
+					$errors .= '</div>';
+					$context['post_error']['messages'][] = $errors;
+
+					// Take out the trash.
+					unset($_SESSION['temp_attachments'][$attachID]);
+					if (file_exists($attachment['tmp_name']))
+						unlink($attachment['tmp_name']);
+					continue;
+				}
 
-					// Too big!  Maybe you could zip it or something...
-					if ($_FILES['attachment']['size'][$n] + $dirSize > $modSettings['attachmentDirSizeLimit'] * 1024)
-						fatal_lang_error('ran_out_of_space');
+				// More house keeping.
+				if (!file_exists($attachment['tmp_name']))
+				{
+					unset($_SESSION['temp_attachments'][$attachID]);
+					continue;
 				}
 
-				if (!is_writable($current_attach_dir))
-					fatal_lang_error('attachments_no_write', 'critical');
+				$context['attachments']['quantity']++;
+				$context['attachments']['total_size'] += $attachment['size'];
+				if (!isset($context['files_in_session_warning']))
+					$context['files_in_session_warning'] = $txt['attached_files_in_session'];
 
-				$attachID = 'post_tmp_' . $user_info['id'] . '_' . $temp_start++;
-				$_SESSION['temp_attachments'][$attachID] = basename($_FILES['attachment']['name'][$n]);
 				$context['current_attachments'][] = array(
-					'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])),
+					'name' => '<u>' . htmlspecialchars($attachment['name']) . '</u>',
+					'size' => $attachment['size'],
 					'id' => $attachID,
+					'unchecked' => false,
 					'approved' => 1,
 				);
-
-				$destName = $current_attach_dir . '/' . $attachID;
-
-				if (!move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
-					fatal_lang_error('attach_timeout', 'critical');
-				@chmod($destName, 0644);
 			}
+		}
 	}
 
+	if (!empty($context['post_error']['messages']) && (isset($newRepliesError) || isset($oldTopicError)))
+		$context['post_error']['messages'][] = '<br />';
+
 	// If we are coming here to make a reply, and someone has already replied... make a special warning message.
 	if (isset($newRepliesError))
 	{
@@ -1051,12 +1057,6 @@ function Post()
 	if (WIRELESS)
 		$context['linktree'][count($context['linktree']) - 1]['url'] = $scripturl . '?action=post;' . (!empty($topic) ? 'topic=' . $topic : 'board=' . $board) . '.' . $_REQUEST['start'] . (isset($_REQUEST['msg']) ? ';msg=' . (int) $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'] : '');
 
-	// If they've unchecked an attachment, they may still want to attach that many more files, but don't allow more than num_allowed_attachments.
-	// @todo This won't work if you're posting an event.
-	$context['num_allowed_attachments'] = empty($modSettings['attachmentNumPerPostLimit']) ? 50 : min($modSettings['attachmentNumPerPostLimit'] - count($context['current_attachments']) + (isset($deleted_attachments) ? $deleted_attachments : 0), $modSettings['attachmentNumPerPostLimit']);
-	$context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments'))) && $context['num_allowed_attachments'] > 0;
-	$context['can_post_attachment_unapproved'] = allowedTo('post_attachment');
-
 	$context['subject'] = addcslashes($form_subject, '"');
 	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
 
@@ -1129,16 +1129,26 @@ function Post()
 	// If the user can post attachments prepare the warning labels.
 	if ($context['can_post_attachment'])
 	{
-		$context['allowed_extensions'] = strtr($modSettings['attachmentExtensions'], array(',' => ', '));
+		// If they've unchecked an attachment, they may still want to attach that many more files, but don't allow more than num_allowed_attachments.
+		$context['num_allowed_attachments'] = empty($modSettings['attachmentNumPerPostLimit']) ? 50 : min($modSettings['attachmentNumPerPostLimit'] - count($context['current_attachments']), $modSettings['attachmentNumPerPostLimit']);
+		$context['can_post_attachment_unapproved'] = allowedTo('post_attachment');
 		$context['attachment_restrictions'] = array();
+		$context['allowed_extensions'] = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
 		$attachmentRestrictionTypes = array('attachmentNumPerPostLimit', 'attachmentPostLimit', 'attachmentSizeLimit');
 		foreach ($attachmentRestrictionTypes as $type)
 			if (!empty($modSettings[$type]))
-				$context['attachment_restrictions'][] = sprintf($txt['attach_restrict_' . $type], $modSettings[$type]);
+			{
+				$context['attachment_restrictions'][] = sprintf($txt['attach_restrict_' . $type], comma_format($modSettings[$type], 0));
+				// Show some numbers. If they exist.
+				if ($type == 'attachmentNumPerPostLimit' && $context['attachments']['quantity'] > 0)
+					$context['attachment_restrictions'][] = sprintf($txt['attach_remaining'], $modSettings['attachmentNumPerPostLimit'] - $context['attachments']['quantity']);
+				elseif ($type == 'attachmentPostLimit' && $context['attachments']['total_size'] > 0)
+					$context['attachment_restrictions'][] = sprintf($txt['attach_available'], comma_format(round(max($modSettings['attachmentPostLimit'] - ($context['attachments']['total_size'] / 1028), 0)), 0));
+			}
 	}
 
 	$context['back_to_topic'] = isset($_REQUEST['goback']) || (isset($_REQUEST['msg']) && !isset($_REQUEST['subject']));
-	$context['show_additional_options'] = !empty($_POST['additional_options']) || !empty($_SESSION['temp_attachments']) || !empty($deleted_attachments);
+	$context['show_additional_options'] = !empty($_POST['additional_options']) || isset($_SESSION['temp_attachments']['post']) || isset($_GET['additionalOptions']);
 
 	$context['is_new_topic'] = empty($topic);
 	$context['is_new_post'] = !isset($_REQUEST['msg']);
@@ -1190,13 +1200,30 @@ function Post2()
 
 	// Sneaking off, are we?
 	if (empty($_POST) && empty($topic))
-		redirectexit('action=post;board=' . $board . '.0');
+	{
+		if (empty($_SERVER['CONTENT_LENGTH']))
+			redirectexit('action=post;board=' . $board . '.0');
+		else
+			fatal_lang_error('post_upload_error', false);
+	}
 	elseif (empty($_POST) && !empty($topic))
 		redirectexit('action=post;topic=' . $topic . '.0');
 
 	// No need!
 	$context['robot_no_index'] = true;
 
+	// Any files to include for post?
+	if (!empty($modSettings['integrate_post_include']))
+	{
+		$post_includes = explode(',', $modSettings['integrate_post_include']);
+		foreach ($post_includes as $include)
+		{
+			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
+			if (file_exists($include))
+				require_once($include);
+		}
+	}
+
 	// If we came from WYSIWYG then turn it back into BBC regardless.
 	if (!empty($_REQUEST['message_mode']) && isset($_REQUEST['message']))
 	{
@@ -1240,6 +1267,227 @@ function Post2()
 	require_once($sourcedir . '/Subs-Post.php');
 	loadLanguage('Post');
 
+	// First check to see if they are trying to delete any current attachments.
+	if (isset($_POST['attach_del']))
+	{
+		$keep_temp = array();
+		$keep_ids = array();
+		foreach ($_POST['attach_del'] as $dummy)
+			if (strpos($dummy, 'post_tmp_' . $user_info['id']) !== false)
+				$keep_temp[] = $dummy;
+			else
+				$keep_ids[] = (int) $dummy;
+
+		if (isset($_SESSION['temp_attachments']))
+			foreach($_SESSION['temp_attachments'] as $attachID => $attachment)
+			{
+				if ((isset($_SESSION['temp_attachments']['post']['files'], $attachment['name']) && in_array($attachment['name'], $_SESSION['temp_attachments']['post']['files'])) || in_array($attachID, $keep_temp) || strpos($attachID, 'post_tmp_' . $user_info['id']) === false)
+					continue;
+
+				unset($_SESSION['temp_attachments'][$attachID]);
+				unlink($attachment['tmp_name']);
+			}
+
+		if (!empty($_REQUEST['msg']))
+		{
+			require_once($sourcedir . '/ManageAttachments.php');
+			$attachmentQuery = array(
+				'attachment_type' => 0,
+				'id_msg' => (int) $_REQUEST['msg'],
+				'not_id_attach' => $keep_ids,
+			);
+			removeAttachments($attachmentQuery);
+		}
+	}
+
+	// Then try to upload any attachments.
+	$context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments')));
+	if ($context['can_post_attachment'] && !empty($_FILES['attachment']) && empty($_POST['from_qr']))
+	{
+		// Make sure we're uploading to the right place.
+		if (!empty($modSettings['currentAttachmentUploadDir']))
+		{
+			if (!is_array($modSettings['attachmentUploadDir']))
+				$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+			// The current directory, of course!
+			$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
+		}
+		else
+			$context['attach_dir'] = $modSettings['attachmentUploadDir'];
+
+		// Is the attachments folder actualy there?
+		if (!is_dir($context['attach_dir']))
+		{
+			$initial_error = 'attach_folder_warning';
+			log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical');
+		}
+
+		// Check that the attachments folder is writable. No sense in proceeding if it isn't.
+		if (empty($initial_error) && !is_writable($context['attach_dir']))
+		{
+			// But, let's try to make it writable first.
+			chmod($context['attach_dir'], 0755);
+			if (!is_writable($context['attach_dir']))
+			{
+				chmod($context['attach_dir'], 0775);
+				if (!is_writable($context['attach_dir']))
+				{
+					chmod($context['attach_dir'], 0777);
+					if (!is_writable($context['attach_dir']))
+						$initial_error = 'attachments_no_write';
+				}
+			}
+		}
+
+		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],
+					'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).
+	//   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());
+
 	// If this isn't a new topic load the topic info that we need.
 	if (!empty($topic))
 	{
@@ -1636,167 +1884,77 @@ function Post2()
 		$_POST['options'] = htmlspecialchars__recursive($_POST['options']);
 	}
 
-	// Check if they are trying to delete any current attachments....
-	if (isset($_REQUEST['msg'], $_POST['attach_del']) && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments'))))
-	{
-		// @todo check attach_del; this will be executed when:
-		// modify a post with attachments; try to remove one, but receive an error
-		// (i.e. body empty); then check back the attachment
-
-		$del_temp = array();
-		foreach ($_POST['attach_del'] as $i => $dummy)
-			$del_temp[$i] = (int) $dummy;
-
-		require_once($sourcedir . '/ManageAttachments.php');
-		$attachmentQuery = array(
-			'attachment_type' => 0,
-			'id_msg' => (int) $_REQUEST['msg'],
-			'not_id_attach' => $del_temp,
-		);
-		removeAttachments($attachmentQuery);
-	}
 
 	// ...or attach a new file...
-	if (isset($_FILES['attachment']['name']) || (!empty($_SESSION['temp_attachments']) && empty($_POST['from_qr'])))
+	if (empty($ignore_temp) && $context['can_post_attachment'] && !empty($_SESSION['temp_attachments']) && empty($_POST['from_qr']))
 	{
-		// Verify they can post them!
-		if (!$modSettings['postmod_active'] || !allowedTo('post_unapproved_attachments'))
-			isAllowedTo('post_attachment');
-
-		// Make sure we're uploading to the right place.
-		if (!empty($modSettings['currentAttachmentUploadDir']))
-		{
-			if (!is_array($modSettings['attachmentUploadDir']))
-				$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
-
-			// The current directory, of course!
-			$current_attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
-		}
-		else
-			$current_attach_dir = $modSettings['attachmentUploadDir'];
-
-		// 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 ($quantity, $total_size) = $smcFunc['db_fetch_row']($request);
-			$smcFunc['db_free_result']($request);
-		}
-		else
-		{
-			$quantity = 0;
-			$total_size = 0;
-		}
-
-		if (!empty($_SESSION['temp_attachments']))
-			foreach ($_SESSION['temp_attachments'] as $attachID => $name)
-			{
-				if (preg_match('~^post_tmp_' . $user_info['id'] . '_\d+$~', $attachID) == 0)
-					continue;
-
-				if (!empty($_POST['attach_del']) && !in_array($attachID, $_POST['attach_del']))
-				{
-					unset($_SESSION['temp_attachments'][$attachID]);
-					@unlink($current_attach_dir . '/' . $attachID);
-					continue;
-				}
-
-				$_FILES['attachment']['tmp_name'][] = $attachID;
-				$_FILES['attachment']['name'][] = $name;
-				$_FILES['attachment']['size'][] = filesize($current_attach_dir . '/' . $attachID);
-				list ($_FILES['attachment']['width'][], $_FILES['attachment']['height'][]) = @getimagesize($current_attach_dir . '/' . $attachID);
-
-				unset($_SESSION['temp_attachments'][$attachID]);
-			}
-
-		if (!isset($_FILES['attachment']['name']))
-			$_FILES['attachment']['tmp_name'] = array();
-
 		$attachIDs = array();
-		foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
+		$attach_errors = array();
+		if (!empty($context['we_are_history']))
+			$attach_errors[] = '<dd>' . $txt['error_temp_attachments_flushed'] . '<br /><br /></dd>';
+
+		foreach ($_SESSION['temp_attachments'] as  $attachID => $attachment)
 		{
-			if ($_FILES['attachment']['name'][$n] == '')
+			if ($attachID != 'initial_error' && strpos($attachID, 'post_tmp_' . $user_info['id']) === false)
 				continue;
 
-			// Have we reached the maximum number of files we are allowed?
-			$quantity++;
-			if (!empty($modSettings['attachmentNumPerPostLimit']) && $quantity > $modSettings['attachmentNumPerPostLimit'])
+			// If there was an initial error just show that message.
+			if ($attachID == 'initial_error')
 			{
-				checkSubmitOnce('free');
-				fatal_lang_error('attachments_limit_per_post', false, array($modSettings['attachmentNumPerPostLimit']));
-			}
+				$attach_errors[] = '<dt>' . $txt['attach_no_upload'] . '</dt>';
+				$attach_errors[] = '<dd>' . (is_array($attachment) ? vsprintf($txt[$attachment[0]], $attachment[1]) : $txt[$attachment]) . '</dd>';
 
-			// Check the total upload size for this post...
-			$total_size += $_FILES['attachment']['size'][$n];
-			if (!empty($modSettings['attachmentPostLimit']) && $total_size > $modSettings['attachmentPostLimit'] * 1024)
-			{
-				checkSubmitOnce('free');
-				fatal_lang_error('file_too_big', false, array($modSettings['attachmentPostLimit']));
+				unset($_SESSION['temp_attachments']);
+				break;
 			}
 
 			$attachmentOptions = array(
 				'post' => isset($_REQUEST['msg']) ? $_REQUEST['msg'] : 0,
 				'poster' => $user_info['id'],
-				'name' => $_FILES['attachment']['name'][$n],
-				'tmp_name' => $_FILES['attachment']['tmp_name'][$n],
-				'size' => $_FILES['attachment']['size'][$n],
+				'name' => $attachment['name'],
+				'tmp_name' => $attachment['tmp_name'],
+				'size' => isset($attachment['size']) ? $attachment['size'] : 0,
+				'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
 				'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
+				'errors' => $attachment['errors'],
 			);
 
-			if (createAttachment($attachmentOptions))
+			if (empty($attachment['errors']))
 			{
-				$attachIDs[] = $attachmentOptions['id'];
-				if (!empty($attachmentOptions['thumb']))
-					$attachIDs[] = $attachmentOptions['thumb'];
-			}
-			else
-			{
-				if (in_array('could_not_upload', $attachmentOptions['errors']))
+				if (createAttachment($attachmentOptions))
 				{
-					checkSubmitOnce('free');
-					fatal_lang_error('attach_timeout', 'critical');
+					$attachIDs[] = $attachmentOptions['id'];
+					if (!empty($attachmentOptions['thumb']))
+						$attachIDs[] = $attachmentOptions['thumb'];
 				}
-				if (in_array('too_large', $attachmentOptions['errors']))
-				{
-					checkSubmitOnce('free');
-					fatal_lang_error('file_too_big', false, array($modSettings['attachmentSizeLimit']));
-				}
-				if (in_array('bad_extension', $attachmentOptions['errors']))
-				{
-					checkSubmitOnce('free');
-					fatal_error($attachmentOptions['name'] . '.<br />' . $txt['cant_upload_type'] . ' ' . $modSettings['attachmentExtensions'] . '.', false);
-				}
-				if (in_array('directory_full', $attachmentOptions['errors']))
-				{
-					checkSubmitOnce('free');
-					fatal_lang_error('ran_out_of_space', 'critical');
-				}
-				if (in_array('bad_filename', $attachmentOptions['errors']))
-				{
-					checkSubmitOnce('free');
-					fatal_error(basename($attachmentOptions['name']) . '.<br />' . $txt['restricted_filename'] . '.', 'critical');
-				}
-				if (in_array('taken_filename', $attachmentOptions['errors']))
-				{
-					checkSubmitOnce('free');
-					fatal_lang_error('filename_exists');
-				}
-				if (in_array('bad_attachment', $attachmentOptions['errors']))
+			}
+
+			if (!empty($attachmentOptions['errors']))
+			{
+				if (isset($br))
+					$attach_errors[] = '<dt>&nbsp;</dt>';
+				else
+					$br = '';
+
+				// Sort out the errors for display and delete any associated files.
+				$attach_errors[] = '<dt>' . vsprintf($txt['attach_warning'], $attachment['name']) . '</dt>';
+				$log_these = array('attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file');
+				foreach ($attachmentOptions['errors'] as $error)
 				{
-					checkSubmitOnce('free');
-					fatal_lang_error('bad_attachment');
+					if (!is_array($error))
+					{
+						$attach_errors[] = '<dd>' . $txt[$error] . '</dd>';
+						if (in_array($error, $log_these))
+							log_error($attachment['name'] . ': ' . $txt[$error], 'critical');
+					}
+					else
+						$attach_errors[] = '<dd>' . vsprintf($txt[$error[0]], $error[1]) . '</dd>';
 				}
+				if (file_exists($attachment['tmp_name']))
+					unlink($attachment['tmp_name']);
 			}
 		}
+		unset($_SESSION['temp_attachments']);
 	}
 
 	// Make the poll...
@@ -2072,6 +2230,35 @@ function Post2()
 	if (!empty($_POST['move']) && allowedTo('move_any'))
 		redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback'));
 
+	// If there are attachment errors. Let's show a list to the user.
+	if (!empty($attach_errors))
+	{
+		global $settings, $scripturl;
+
+		loadTemplate('Errors');
+		$context['sub_template'] = 'attachment_errors';
+		$context['page_title'] = $txt['error_occured'];
+
+		$context['error_message'] = '<dl>';
+		$context['error_message'] .= implode("\n", $attach_errors);
+		$context['error_message'] .= '</dl>';
+
+		$context['linktree'][] = array(
+			'url' => $scripturl . '?topic=' . $topic . '.0',
+			'name' => $_POST['subject'],
+			'extra_before' => $settings['linktree_inline'] ? $txt['topic'] . ': ' : ''
+		);
+
+		if (isset($_REQUEST['msg']))
+			$context['redirect_link'] = '?topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'];
+		else
+			$context['redirect_link'] = '?topic=' . $topic . '.new#new';
+
+		$context['back_link'] = '?action=post;msg=' . $msgOptions['id'] . ';topic=' . $topic . ';additionalOptions#postAttachment';
+
+		obExit(null, true);
+	}
+
 	// Return to post if the mod is on.
 	if (isset($_REQUEST['msg']) && !empty($_REQUEST['goback']))
 		redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'], isBrowser('ie'));

+ 2 - 2
Sources/Subs-Graphics.php

@@ -282,7 +282,7 @@ function imageMemoryCheck($sizes)
 	// doing the old 'set it and hope' way?
 	if (empty($modSettings['attachment_thumb_memory']))
 	{
-		setMemoryLimit('90M');
+		setMemoryLimit('128M');
 		return true;
 	}
 
@@ -358,7 +358,7 @@ function resizeImageFile($source, $destination, $max_width, $max_height, $prefer
 	else
 		$sizes = array(-1, -1, -1);
 		
-	// See if we have -or- can get the need memory for this operation
+	// See if we have -or- can get the needed memory for this operation
 	if (!imageMemoryCheck($sizes))
 		return false;
 

+ 168 - 212
Sources/Subs-Post.php

@@ -2042,49 +2042,23 @@ function createPost(&$msgOptions, &$topicOptions, &$posterOptions)
 
 /**
  * Create an attachment, with the given array of parameters.
+ * - Adds any addtional or missing parameters to $attachmentOptions.
+ * - Renames the temporary file.
+ * - Creates a thumbnail if the file is an image and the option enabled.
  *
  * @param array $attachmentOptions
  */
 function createAttachment(&$attachmentOptions)
 {
 	global $modSettings, $sourcedir, $smcFunc, $context;
+	global $txt, $boarddir;
 
 	require_once($sourcedir . '/Subs-Graphics.php');
 
-	// We need to know where this thing is going.
 	if (!empty($modSettings['currentAttachmentUploadDir']))
-	{
-		if (!is_array($modSettings['attachmentUploadDir']))
-			$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
-
-		// Just use the current path for temp files.
-		$attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
 		$id_folder = $modSettings['currentAttachmentUploadDir'];
-	}
 	else
-	{
-		$attach_dir = $modSettings['attachmentUploadDir'];
 		$id_folder = 1;
-	}
-
-	$attachmentOptions['errors'] = array();
-	if (!isset($attachmentOptions['post']))
-		$attachmentOptions['post'] = 0;
-	if (!isset($attachmentOptions['approved']))
-		$attachmentOptions['approved'] = 1;
-
-	$already_uploaded = preg_match('~^post_tmp_' . $attachmentOptions['poster'] . '_\d+$~', $attachmentOptions['tmp_name']) != 0;
-	$file_restricted = ini_get('open_basedir') != '' && !$already_uploaded;
-
-	if ($already_uploaded)
-		$attachmentOptions['tmp_name'] = $attach_dir . '/' . $attachmentOptions['tmp_name'];
-
-	// Make sure the file actually exists... sometimes it doesn't.
-	if ((!$file_restricted && !file_exists($attachmentOptions['tmp_name'])) || (!$already_uploaded && !is_uploaded_file($attachmentOptions['tmp_name'])))
-	{
-		$attachmentOptions['errors'] = array('could_not_upload');
-		return false;
-	}
 
 	// These are the only valid image types for SMF.
 	$validImageTypes = array(
@@ -2099,104 +2073,25 @@ function createAttachment(&$attachmentOptions)
 		14 => 'iff'
 	);
 
-	if (!$file_restricted || $already_uploaded)
-	{
-		$size = @getimagesize($attachmentOptions['tmp_name']);
-		list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
+	// If this is an image we need to set a few additional parameters.
+	$size = @getimagesize($attachmentOptions['tmp_name']);
+	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
 
-		// If it's an image get the mime type right.
-		if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
-		{
-			// Got a proper mime type?
-			if (!empty($size['mime']))
-				$attachmentOptions['mime_type'] = $size['mime'];
-			// Otherwise a valid one?
-			elseif (isset($validImageTypes[$size[2]]))
-				$attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]];
-		}
+	// If it's an image get the mime type right.
+	if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
+	{
+		// Got a proper mime type?
+		if (!empty($size['mime']))
+			$attachmentOptions['mime_type'] = $size['mime'];
+		// Otherwise a valid one?
+		elseif (isset($validImageTypes[$size[2]]))
+			$attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]];
 	}
 
 	// Get the hash if no hash has been given yet.
 	if (empty($attachmentOptions['file_hash']))
 		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
 
-	// Is the file too big?
-	if (!empty($modSettings['attachmentSizeLimit']) && $attachmentOptions['size'] > $modSettings['attachmentSizeLimit'] * 1024)
-		$attachmentOptions['errors'][] = 'too_large';
-
-	if (!empty($modSettings['attachmentCheckExtensions']))
-	{
-		$allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
-		foreach ($allowed as $k => $dummy)
-			$allowed[$k] = trim($dummy);
-
-		if (!in_array(strtolower(substr(strrchr($attachmentOptions['name'], '.'), 1)), $allowed))
-			$attachmentOptions['errors'][] = 'bad_extension';
-	}
-
-	if (!empty($modSettings['attachmentDirSizeLimit']))
-	{
-		// Make sure the directory isn't full.
-		$dirSize = 0;
-		$dir = @opendir($attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical');
-		while ($file = readdir($dir))
-		{
-			if ($file == '.' || $file == '..')
-				continue;
-
-			if (preg_match('~^post_tmp_\d+_\d+$~', $file) != 0)
-			{
-				// Temp file is more than 5 hours old!
-				if (filemtime($attach_dir . '/' . $file) < time() - 18000)
-					@unlink($attach_dir . '/' . $file);
-				continue;
-			}
-
-			$dirSize += filesize($attach_dir . '/' . $file);
-		}
-		closedir($dir);
-
-		// Too big!  Maybe you could zip it or something...
-		if ($attachmentOptions['size'] + $dirSize > $modSettings['attachmentDirSizeLimit'] * 1024)
-			$attachmentOptions['errors'][] = 'directory_full';
-		// Soon to be too big - warn the admins...
-		elseif (!isset($modSettings['attachment_full_notified']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $attachmentOptions['size'] + $dirSize > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024)
-		{
-			require_once($sourcedir . '/Subs-Admin.php');
-			emailAdmins('admin_attachments_full');
-			updateSettings(array('attachment_full_notified' => 1));
-		}
-	}
-
-	// Check if the file already exists.... (for those who do not encrypt their filenames...)
-	if (empty($modSettings['attachmentEncryptFilenames']))
-	{
-		// Make sure they aren't trying to upload a nasty file.
-		$disabledFiles = array('con', 'com1', 'com2', 'com3', 'com4', 'prn', 'aux', 'lpt1', '.htaccess', 'index.php');
-		if (in_array(strtolower(basename($attachmentOptions['name'])), $disabledFiles))
-			$attachmentOptions['errors'][] = 'bad_filename';
-
-		// Check if there's another file with that name...
-		$request = $smcFunc['db_query']('', '
-			SELECT id_attach
-			FROM {db_prefix}attachments
-			WHERE filename = {string:filename}
-			LIMIT 1',
-			array(
-				'filename' => strtolower($attachmentOptions['name']),
-			)
-		);
-		if ($smcFunc['db_num_rows']($request) > 0)
-			$attachmentOptions['errors'][] = 'taken_filename';
-		$smcFunc['db_free_result']($request);
-	}
-
-	if (!empty($attachmentOptions['errors']))
-		return false;
-
-	if (!is_writable($attach_dir))
-		fatal_lang_error('attachments_no_write', 'critical');
-
 	// Assuming no-one set the extension let's take a look at it.
 	if (empty($attachmentOptions['fileext']))
 	{
@@ -2221,10 +2116,15 @@ function createAttachment(&$attachmentOptions)
 	);
 	$attachmentOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
 
+	// @todo Add an error here maybe?
 	if (empty($attachmentOptions['id']))
 		return false;
 
-	// If it's not approved add to the approval queue.
+	// Now that we have the attach id, let's rename this sucker and finish up.
+	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $id_folder, false, $attachmentOptions['file_hash']);
+	rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
+
+	// If it's not approved then add to the approval queue.
 	if (!$attachmentOptions['approved'])
 		$smcFunc['db_insert']('',
 			'{db_prefix}approval_queue',
@@ -2237,99 +2137,11 @@ function createAttachment(&$attachmentOptions)
 			array()
 		);
 
-	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $id_folder, false, $attachmentOptions['file_hash']);
-
-	if ($already_uploaded)
-		rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
-	elseif (!move_uploaded_file($attachmentOptions['tmp_name'], $attachmentOptions['destination']))
-		fatal_lang_error('attach_timeout', 'critical');
-
-	// Attempt to chmod it.
-	@chmod($attachmentOptions['destination'], 0644);
-
-	$size = @getimagesize($attachmentOptions['destination']);
-	list ($attachmentOptions['width'], $attachmentOptions['height']) = empty($size) ? array(null, null, null) : $size;
-
-	// We couldn't access the file before...
-	if ($file_restricted)
-	{
-		// Have a go at getting the right mime type.
-		if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
-		{
-			if (!empty($size['mime']))
-				$attachmentOptions['mime_type'] = $size['mime'];
-			elseif (isset($validImageTypes[$size[2]]))
-				$attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]];
-		}
-
-		if (!empty($attachmentOptions['width']) && !empty($attachmentOptions['height']))
-			$smcFunc['db_query']('', '
-				UPDATE {db_prefix}attachments
-				SET
-					width = {int:width},
-					height = {int:height},
-					mime_type = {string:mime_type}
-				WHERE id_attach = {int:id_attach}',
-				array(
-					'width' => (int) $attachmentOptions['width'],
-					'height' => (int) $attachmentOptions['height'],
-					'id_attach' => $attachmentOptions['id'],
-					'mime_type' => empty($attachmentOptions['mime_type']) ? '' : $attachmentOptions['mime_type'],
-				)
-			);
-	}
-
-	// Security checks for images
-	// Do we have an image? If yes, we need to check it out!
-	if (isset($validImageTypes[$size[2]]))
-	{
-		if (!checkImageContents($attachmentOptions['destination'], !empty($modSettings['attachment_image_paranoid'])))
-		{
-			// It's bad. Last chance, maybe we can re-encode it?
-			if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($attachmentOptions['destination'], $size[2])))
-			{
-				// Nothing to do: not allowed or not successful re-encoding it.
-				require_once($sourcedir . '/ManageAttachments.php');
-				removeAttachments(array(
-					'id_attach' => $attachmentOptions['id']
-				));
-				$attachmentOptions['id'] = null;
-				$attachmentOptions['errors'][] = 'bad_attachment';
-
-				return false;
-			}
-			// Success! However, successes usually come for a price:
-			// we might get a new format for our image...
-			$old_format = $size[2];
-			$size = @getimagesize($attachmentOptions['destination']);
-			if (!(empty($size)) && ($size[2] != $old_format))
-			{
-				// Let's update the image information
-				// @todo This is becoming a mess: we keep coming back and update the database,
-				// instead of getting it right the first time.
-				if (isset($validImageTypes[$size[2]]))
-				{
-					$attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]];
-					$smcFunc['db_query']('', '
-						UPDATE {db_prefix}attachments
-						SET
-							mime_type = {string:mime_type}
-						WHERE id_attach = {int:id_attach}',
-						array(
-							'id_attach' => $attachmentOptions['id'],
-							'mime_type' => $attachmentOptions['mime_type'],
-						)
-					);
-				}
-			}
-		}
-	}
-
-	if (!empty($attachmentOptions['skip_thumbnail']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
+	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
 		return true;
 
 	// Like thumbnails, do we?
-	if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
+	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
 	{
 		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
 		{
@@ -2380,6 +2192,150 @@ function createAttachment(&$attachmentOptions)
 			}
 		}
 	}
+	return true;
+}
+
+/**
+ * Performs various checks on an uploaded file.
+ * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
+ *
+ * @param $attachID
+ */
+function attachmentChecks($attachID)
+{
+	global $modSettings, $context, $sourcedir, $smcFunc;
+
+	// No data or missing data .... Not necessarily needed, but in case a mod author missed something.
+	if ( empty($_SESSION['temp_attachments'][$attachID]))
+		$errror = '$_SESSION[\'temp_attachments\'][$attachID]';
+	elseif (empty($attachID))
+		$errror = '$attachID';
+	elseif (empty($context['attachments']))
+		$errror = '$context[\'attachments\']';
+	elseif (empty($context['attach_dir']))
+		$errror = '$context[\'attach_dir\']';
+		
+	// Let's get their attention.
+	if (!empty($error))
+		fatal_lang_error('attach_check_nag', 'debug', array($error));
+
+	// These are the only valid image types for SMF.
+	$validImageTypes = array(
+		1 => 'gif',
+		2 => 'jpeg',
+		3 => 'png',
+		5 => 'psd',
+		6 => 'bmp',
+		7 => 'tiff',
+		8 => 'tiff',
+		9 => 'jpeg',
+		14 => 'iff'
+	);
+
+	// Just in case this slipped by the first checks, we stop it here and now
+	if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
+	{
+		$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
+		return false;
+	}
+
+	// First, the dreaded security check. Sorry folks, but this can't be avoided
+	$size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
+	if (isset($validImageTypes[$size[2]]))
+	{
+		require_once($sourcedir . '/Subs-Graphics.php');
+		if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
+		{
+			// It's bad. Last chance, maybe we can re-encode it?
+			if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
+			{
+				// Nothing to do: not allowed or not successful re-encoding it.
+				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
+				return false;
+			}
+			// Success! However, successes usually come for a price:
+			// we might get a new format for our image...
+			$old_format = $size[2];
+			$size = @getimagesize($attachmentOptions['tmp_name']);
+			if (!(empty($size)) && ($size[2] != $old_format))
+			{
+				if (isset($validImageTypes[$size[2]]))
+					$_SESSION['temp_attachments'][$attachID]['type'] = 'image/' . $validImageTypes[$size[2]];
+			}
+		}
+	}
+
+	if (!empty($modSettings['attachmentDirSizeLimit']))
+	{
+		// Check the folder size if it hasn't been done already.
+		if (!isset($context['dir_size']))
+		{
+			$request = $smcFunc['db_query']('', '
+				SELECT SUM(size)
+				FROM {db_prefix}attachments',
+				array(
+				)
+			);
+			list ($context['dir_size']) = $smcFunc['db_fetch_row']($request);
+			$smcFunc['db_free_result']($request);
+		}
+
+		$context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
+
+		// Soon to be too big - warn the admins...
+		if (empty($modSettings['attachment_full_notified']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024)
+		{
+			require_once($sourcedir . '/Subs-Admin.php');
+			emailAdmins('admin_attachments_full');
+			updateSettings(array('attachment_full_notified' => 1));
+		}
+
+		// Too big!  Maybe you could zip it or something...
+		if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024)
+			$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
+	}
+
+	// Is the file too big?
+	$context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
+	if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
+		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
+
+	// Check the total upload size for this post...
+	if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
+		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attach_max_total_file_size', array(comma_format($modSettings['attachmentPostLimit'], 0), comma_format($modSettings['attachmentPostLimit'] - (($context['attachments']['total_size'] - $_SESSION['temp_attachments'][$attachID]['size']) / 1024), 0)));
+
+	// Have we reached the maximum number of files we are allowed?
+	$context['attachments']['quantity']++;
+	
+	// Set a max limit if none exists
+	if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
+		$modSettings['attachmentNumPerPostLimit'] = 50;
+
+	if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
+		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
+
+	// File extension check
+	if (!empty($modSettings['attachmentCheckExtensions']))
+	{
+		$allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
+		foreach ($allowed as $k => $dummy)
+			$allowed[$k] = trim($dummy);
+
+		if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
+		{
+			$allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
+			$_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
+		}
+	}
+
+	// back up to the previous one if there's been an error.
+	if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
+	{
+		$context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
+		$context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
+		$context['attachments']['quantity']--;
+		return false;
+	}
 
 	return true;
 }

+ 28 - 0
Themes/default/Errors.template.php

@@ -206,4 +206,32 @@ function template_show_file()
 </html>';
 }
 
+function template_attachment_errors()
+{
+	global $context, $scripturl, $txt;
+
+	echo '
+	<div>
+		<div class="cat_bar">
+			<h3 class="catbg">
+				', $txt['attach_error_title'], '
+			</h3>
+		</div>
+		<div class="windowbg">
+			<span class="topslice"><span></span></span>
+			<div class="padding">
+				<div class="noticebox" />', 
+					$context['error_message'], '
+				</div>
+				<hr class="hrcolor" />
+				<a class="button_link" href="', $scripturl, $context['back_link'], '">', $txt['back'], '</a>
+				<span style="float: right; margin:.5em;"></span>
+				<a class="button_link" href="', $scripturl, $context['redirect_link'], '">', $txt['attach_continue'], '</a>
+				<br class="clear_right" />
+			</div>
+			<span class="botslice"><span></span></span>
+		</div>
+	</div>';
+}
+
 ?>

+ 29 - 6
Themes/default/Post.template.php

@@ -407,8 +407,14 @@ function template_main()
 		foreach ($context['current_attachments'] as $attachment)
 			echo '
 						<dd class="smalltext">
-							<label for="attachment_', $attachment['id'], '"><input type="checkbox" id="attachment_', $attachment['id'], '" name="attach_del[]" value="', $attachment['id'], '"', empty($attachment['unchecked']) ? ' checked="checked"' : '', ' class="input_check" /> ', $attachment['name'], (empty($attachment['approved']) ? ' (' . $txt['awaiting_approval'] . ')' : ''), '</label>
+							<label for="attachment_', $attachment['id'], '"><input type="checkbox" id="attachment_', $attachment['id'], '" name="attach_del[]" value="', $attachment['id'], '"', empty($attachment['unchecked']) ? ' checked="checked"' : '', ' class="input_check" /> ', $attachment['name'], (empty($attachment['approved']) ? ' (' . $txt['awaiting_approval'] . ')' : ''),
+							!empty($modSettings['attachmentPostLimit']) || !empty($modSettings['attachmentSizeLimit']) ? sprintf($txt['attach_kb'], comma_format(round(max($attachment['size'], 1028) / 1028), 0)) : '', '</label>
 						</dd>';
+
+		if (!empty($context['files_in_session_warning']))
+			echo '
+						<dd class="smalltext">', $context['files_in_session_warning'], '</dd>';
+
 		echo '
 					</dl>';
 	}
@@ -417,16 +423,22 @@ function template_main()
 	if ($context['can_post_attachment'])
 	{
 		echo '
-					<dl id="postAttachment2">
+					<dl id="postAttachment2">';
+		
+		// But, only show them if they haven't reached a limit. Or a mod author hasn't hidden them.
+		if ($context['num_allowed_attachments'] > 0 || !empty($context['dont_show_them']))
+		{
+			echo '
 						<dt>
 							', $txt['attach'], ':
 						</dt>
 						<dd class="smalltext">
+							', empty($modSettings['attachmentSizeLimit']) ? '' : ('<input type="hidden" name="MAX_FILE_SIZE" value="' . $modSettings['attachmentSizeLimit'] * 1028 . '" />'), '
 							<input type="file" size="60" name="attachment[]" id="attachment1" class="input_file" /> (<a href="javascript:void(0);" onclick="cleanFileInput(\'attachment1\');">', $txt['clean_attach'], '</a>)';
 
-		// Show more boxes only if they aren't approaching their limit.
-		if ($context['num_allowed_attachments'] > 1)
-			echo '
+			// Show more boxes if they aren't approaching that limit.
+			if ($context['num_allowed_attachments'] > 1)
+				echo '
 							<script type="text/javascript"><!-- // --><![CDATA[
 								var allowed_attachments = ', $context['num_allowed_attachments'], ';
 								var current_attachment = 1;
@@ -438,13 +450,20 @@ function template_main()
 									if (allowed_attachments <= 0)
 										return alert("', $txt['more_attachments_error'], '");
 
-									setOuterHTML(document.getElementById("moreAttachments"), \'<dd class="smalltext"><input type="file" size="60" name="attachment[]" id="attachment\' + current_attachment + \'" class="input_file" /> (<a href="javascript:void(0);" onclick="cleanFileInput(\\\'attachment\' + current_attachment + \'\\\');">', $txt['clean_attach'], '</a>)\' + \'</dd><dd class="smalltext" id="moreAttachments"><a href="#" onclick="addAttachment(); return false;">(', $txt['more_attachments'], ')<\' + \'/a><\' + \'/dd>\');
+									setOuterHTML(document.getElementById("moreAttachments"), \'<dd class="smalltext"><input type="file" size="60" name="attachment[]" id="attachment\' + current_attachment + \'" class="input_file" /> (<a href="javascript:void(0);" onclick="cleanFileInput(\\\'attachment\' + current_attachment + \'\\\');">', $txt['clean_attach'], '<\/a>)\' + \'<\/dd><dd class="smalltext" id="moreAttachments"><a href="#" onclick="addAttachment(); return false;">(', $txt['more_attachments'], ')<\' + \'/a><\' + \'/dd>\');
 
 									return true;
 								}
 							// ]]></script>
 						</dd>
 						<dd class="smalltext" id="moreAttachments"><a href="#" onclick="addAttachment(); return false;">(', $txt['more_attachments'], ')</a></dd>';
+			else
+				echo '
+						</dd>';
+		}
+
+		// Add any template changes for an alternative upload system here.
+		call_integration_hook('integrate_upload_template', array());
 
 		echo '
 						<dd class="smalltext">';
@@ -458,6 +477,10 @@ function template_main()
 			echo '
 							', $txt['attach_restrictions'], ' ', implode(', ', $context['attachment_restrictions']), '<br />';
 
+		if ($context['num_allowed_attachments'] == 0)
+			echo '
+							', $txt['attach_limit_nag'], '<br />';
+
 		if (!$context['can_post_attachment_unapproved'])
 			echo '
 							<span class="alert">', $txt['attachment_requires_approval'], '</span>', '<br />';

+ 1 - 9
Themes/default/languages/Errors.english.php

@@ -35,12 +35,6 @@ $txt['cant_change_own_karma'] = 'Sorry, you are not permitted to modify your own
 $txt['karma_wait_time'] = 'Sorry, you can\'t repeat a karma action without waiting %1$s %2$s.';
 $txt['feature_disabled'] = 'Sorry, this feature is disabled.';
 $txt['feature_no_exists'] = 'Sorry, this feature doesn\'t exist.';
-$txt['cant_access_upload_path'] = 'Cannot access attachments upload path!';
-$txt['file_too_big'] = 'Your file is too large. The maximum attachment size allowed is %1$d kB.';
-$txt['attach_timeout'] = 'Your attachment couldn\'t be saved. This might happen because it took too long to upload or the file is bigger than the server will allow.<br /><br />Please consult your server administrator for more information.';
-$txt['filename_exists'] = 'Sorry! There is already an attachment with the same filename as the one you tried to upload. Please rename the file and try again.';
-$txt['bad_attachment'] = 'Your attachment has failed security checks and cannot be uploaded. Please consult the forum administrator.';
-$txt['ran_out_of_space'] = 'The upload folder is full. Please try a smaller file and/or contact an administrator.';
 $txt['couldnt_connect'] = 'Could not connect to server or could not find file';
 $txt['no_board'] = 'The board you specified doesn\'t exist';
 $txt['cant_split'] = 'You are not allowed to split topics';
@@ -215,12 +209,10 @@ $txt['email_missing_data'] = 'You must enter something in both the subject and m
 $txt['topic_gone'] = 'The topic or board you are looking for appears to be either missing or off limits to you.';
 $txt['theme_edit_missing'] = 'The file you are trying to edit... can\'t even be found!';
 
-$txt['attachments_no_write'] = 'The attachments upload directory is not writable.  Your attachment or avatar cannot be saved.';
-$txt['attachments_limit_per_post'] = 'You may not upload more than %1$d attachments per post';
-
 $txt['no_dump_database'] = 'Only administrators can make database backups!';
 $txt['pm_not_yours'] = 'The personal message you\'re trying to quote is not your own or does not exist, please go back and try again.';
 $txt['mangled_post'] = 'Mangled form data - please go back and try again.';
+$txt['post_upload_error'] = 'The post data is missing. This error is can be caused by trying to submit to file larger than allowed by the server.  Please contact your administrator if this problem continues.';
 $txt['quoted_post_deleted'] = 'The post you are trying to quote either does not exist, was deleted, or is no longer viewable by you.';
 $txt['pm_too_many_per_hour'] = 'You have exceeded the limit of %1$d personal messages per hour.';
 $txt['labels_too_many'] = 'Sorry, %1$s messages already had the maximum amount of labels allowed!';

+ 37 - 1
Themes/default/languages/Post.english.php

@@ -94,7 +94,7 @@ $txt['attach'] = 'Attach';
 $txt['clean_attach'] = 'Clear Attachment';
 $txt['attached'] = 'Attached';
 $txt['allowed_types'] = 'Allowed file types';
-$txt['cant_upload_type'] = 'You cannot upload that type of file. The only allowed extensions are';
+$txt['cant_upload_type'] = 'You cannot upload that type of file. The only allowed extensions are %1$s.';
 $txt['uncheck_unwatchd_attach'] = 'Uncheck the attachments you no longer want attached';
 $txt['restricted_filename'] = 'That is a restricted filename. Please try a different filename.';
 $txt['topic_locked_no_reply'] = 'Warning: topic is currently/will be locked!<br />Only admins and moderators can reply.';
@@ -194,4 +194,40 @@ $txt['digest_mod_act_move'] = '"%1$s" was moved';
 $txt['digest_mod_act_merge'] = '"%1$s" was merged';
 $txt['digest_mod_act_split'] = '"%1$s" was split';
 
+$txt['attach_error_title'] = 'Error uploading your attachments.';
+$txt['attach_warning'] = 'There was a problem during the uploading of <strong>%1$s</strong>.';
+$txt['attach_check_nag'] = 'Unable to continue due to incomplete data (%1$s).';
+$txt['attach_continue'] = 'Continue';
+$txt['attach_max_total_file_size'] = 'Sorry, you are out of attachment space. The total attachment size allowed per post is %1$s kB. Space remaining is %2$s kB.';
+$txt['attach_folder_warning'] = 'The attachments directory can not be located. Please notify an administrator of this problem.';
+$txt['attach_folder_admin_warning'] = 'The path to the attachments directory (%1$s) is incorrect. Please correct it in the attachment settings.';
+$txt['attach_limit_nag'] = 'Sorry, but you have reached the maximum number of attachments allowed per post.';
+$txt['attach_no_upload'] = 'There was a problem and your attachments could not be uploaded';
+$txt['attach_remaining'] = '%1$d remaining';
+$txt['attach_available'] = '%1$s kB available';
+$txt['attach_kb'] = ' (%1$s kB)';
+$txt['attach_0_byte_file'] = 'This file appears to be empty. Please contact your forum administrator if this continues to be a problem';
+$txt['attached_files_in_session'] = '<em>The above underlined file(s) have been uploaded but will not be attached to this post until it is submitted.</em>';
+
+$txt['attach_php_error'] = 'Due to an error, your attachment could not be uploaded. Please contact the forum administrator if this continues to be a problem.';
+$txt['php_upload_error_1'] = 'The uploaded file exceeds the upload_max_filesize directive in php.ini. Please contact your host if unable to rectify.';
+$txt['php_upload_error_3'] = 'The uploaded file was only partially uploaded. This is a PHP related error. Please contact your host if this problem continues.';
+$txt['php_upload_error_4'] = 'No file was uploaded. This is a PHP related error. Please contact your host if this problem continues.';
+$txt['php_upload_error_6'] = 'Unable to save. Missing a temporary folder. Please contact your host if unable to rectify.';
+$txt['php_upload_error_7'] = 'Failed to write file to disk. This is a PHP related error. Please contact your host if this problem continues.';
+$txt['php_upload_error_8'] = 'A PHP extension stopped the file upload. This is a PHP related error. Please contact your host if this problem continues.';
+$txt['error_temp_attachments_new'] = 'There are attachments which you had previously attached but not posted. These attachments are still attached to this post. This post does need to be submitted before these attachments are either saved or removed. You can do that <a href="#postAttachment">here</a>';
+$txt['error_temp_attachments_found'] = 'The following attachments were found which you had previously attached to another post but not posted. It is advisable that you do not post until these are either removed or that post has been submited.<br />Click %1$s to remove those attachments. Or %2$s to return to that post.%3$s';
+$txt['error_temp_attachments_lost'] = 'The following attachments were found which you had previously attached to another post but not posted. It is advisable that you do not upload any more attachments until these are removed or that post has been submitedd.<br />Click %1$s to remove these attachments.%2$s';
+$txt['error_temp_attachments_gone'] = 'Those attachments are now removed and you have been returned to the page you were previously on';
+$txt['error_temp_attachments_flushed'] = 'Please note that any files which had been previously attached but not posted. Have now been removed.';
+
+$txt['cant_access_upload_path'] = 'Cannot access attachments upload path!';
+$txt['file_too_big'] = 'Your file is too large. The maximum attachment size allowed is %1$d kB.';
+$txt['attach_timeout'] = 'Your attachment couldn\'t be saved. This might happen because it took too long to upload or the file is bigger than the server will allow.<br /><br />Please consult your server administrator for more information.';
+$txt['bad_attachment'] = 'Your attachment has failed security checks and cannot be uploaded. Please consult the forum administrator.';
+$txt['ran_out_of_space'] = 'The upload folder is full. Please try a smaller file and/or contact an administrator.';
+$txt['attachments_no_write'] = 'The attachments upload directory is not writable.  Your attachment or avatar cannot be saved.';
+$txt['attachments_limit_per_post'] = 'You may not upload more than %1$d attachments per post';
+
 ?>