Browse Source

Merge pull request #79 from Kays48/attachment_folder_update

Conflicts:
	Sources/Load.php
	Sources/ManageServer.php
	Sources/Post.php
	Themes/default/index.template.php
emanuele 12 years ago
parent
commit
2e9aa34d1d

+ 1 - 0
Sources/Admin.php

@@ -284,6 +284,7 @@ function AdminMain()
 						'browse' => array($txt['attachment_manager_browse']),
 						'attachments' => array($txt['attachment_manager_settings']),
 						'avatars' => array($txt['attachment_manager_avatar_settings']),
+						'attachpaths' => array($txt['attach_directories']),
 						'maintenance' => array($txt['attachment_manager_maintenance']),
 					),
 				),

+ 799 - 0
Sources/Attachments.php

@@ -0,0 +1,799 @@
+<?php
+
+/**
+ * This file handles the uploading and creation of attachments
+ * as well as the auto management of the attachment directories.
+ *
+ * Simple Machines Forum (SMF)
+ *
+ * @package SMF
+ * @author Simple Machines
+ *
+ * @copyright 2012 Simple Machines
+ * @license http://www.simplemachines.org/about/smf/license.php BSD
+ *
+ * @version 2.1 Alpha 1
+ */
+
+if (!defined('SMF'))
+	die('Hacking attempt...');
+
+function automanage_attachments_check_directory()
+{
+	global $boarddir, $modSettings, $context;
+
+	// Not pretty, but since we don't want folders created for every post. It'll do unless a better solution can be found.
+	if (empty($modSettings['automanage_attachments']))
+		return;
+	elseif (!isset($_FILES) && !isset($doit))
+		return;
+	elseif (isset($_FILES))
+		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
+			if (!empty($dummy))
+			{
+				$doit = true;
+				break;
+			}
+
+	if (!isset($doit))
+		return;
+
+	$year = date('Y');
+	$month = date('m');
+	$day = date('d');
+
+	$rand = md5(mt_rand());
+	$rand1 = $rand[1];
+	$rand = $rand[0];
+
+	if (!empty($modSettings['attachment_basedirectories']) && !empty($modSettings['use_subdirectories_for_attachments']))
+	{
+			if (!is_array($modSettings['attachment_basedirectories']))
+				$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
+			$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
+	}
+	else
+		$base_dir = 0;
+
+	if ($modSettings['automanage_attachments'] == 1)
+	{
+		if (!isset($modSettings['last_attachments_directory']))
+			$modSettings['last_attachments_directory'] = array();
+		if (!is_array($modSettings['last_attachments_directory']))
+			$modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
+		if (!isset($modSettings['last_attachments_directory'][$base_dir]))
+			$modSettings['last_attachments_directory'][$base_dir] = 0;
+	}
+
+	$basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
+	//Just to be sure: I don't want directory separators at the end
+	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
+	$basedirectory = rtrim($basedirectory, $sep);
+
+	switch ($modSettings['automanage_attachments']){
+		case 1:
+			$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . (isset($modSettings['last_attachments_directory'][$base_dir]) ? $modSettings['last_attachments_directory'][$base_dir] : 0);
+			break;
+		case 2:
+			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year;
+			break;
+		case 3:
+			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year . DIRECTORY_SEPARATOR . $month;
+			break;
+		case 4:
+			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
+			break;
+		case 5:
+			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand . DIRECTORY_SEPARATOR . $rand1;
+			break;
+		default :
+			$updir = '';
+	}
+
+	if (!is_array($modSettings['attachmentUploadDir']))
+		$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+	if (!in_array($updir, $modSettings['attachmentUploadDir']) && !empty($updir))
+		$outputCreation = automanage_attachments_create_directory($updir);
+	elseif (in_array($updir, $modSettings['attachmentUploadDir']))
+		$outputCreation = true;
+
+	if ($outputCreation)
+	{
+		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
+		$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
+
+		updateSettings(array(
+			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
+		));
+	}
+
+	return $outputCreation;
+}
+
+function automanage_attachments_create_directory($updir)
+{
+	global $modSettings, $initial_error, $context, $boarddir;
+
+	$tree = mama_get_directory_tree_elements($updir);
+	$count = count($tree);
+
+	$directory = mama_init_dir($tree, $count);
+	if ($directory === false)
+	{
+		// Maybe it's just the folder name
+		$tree = mama_get_directory_tree_elements($boarddir . DIRECTORY_SEPARATOR . $updir);
+		$count = count($tree);
+	
+		$directory = mama_init_dir($tree, $count);
+		if ($directory === false)
+			return false;
+	}
+
+	$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
+
+	while (!is_dir($directory) || $count != -1)
+	{
+		if (!is_dir($directory))
+		{
+			if (!@mkdir($directory,0755))
+			{
+				$context['dir_creation_error'] = 'attachments_no_create';
+				return false;
+			}
+		}
+
+		$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
+		$count--;
+	}
+
+	if (!is_writable($directory))
+	{
+		chmod($directory, 0755);
+		if (!is_writable($directory))
+		{
+			chmod($directory, 0775);
+			if (!is_writable($directory))
+			{
+				chmod($directory, 0777);
+				if (!is_writable($directory))
+				{
+					$context['dir_creation_error'] = 'attachments_no_write';
+					return false;
+				}
+			}
+		}
+	}
+
+	// Everything seems fine...let's create the .htaccess
+	if (!file_exists($directory . DIRECTORY_SEPARATOR . '.htacess'))
+		secureDirectory($updir, true);
+
+	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
+	$updir = rtrim($updir, $sep);
+
+	// Only update if it's a new directory
+	if (!in_array($updir, $modSettings['attachmentUploadDir']))
+	{
+		$modSettings['currentAttachmentUploadDir'] = max(array_keys($modSettings['attachmentUploadDir'])) +1;
+		$modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] = $updir;
+
+		updateSettings(array(
+			'attachmentUploadDir' => serialize($modSettings['attachmentUploadDir']),
+			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
+		), true);
+		$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+	}
+
+	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
+	return true;
+}
+
+function automanage_attachments_by_space()
+{
+	global $modSettings, $boarddir, $context;
+
+	if (!isset($modSettings['automanage_attachments']) || (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] != 1))
+		return;
+
+	$basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
+	//Just to be sure: I don't want directory separators at the end
+	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
+	$basedirectory = rtrim($basedirectory, $sep);
+
+	// Get the current base directory
+	if (!empty($modSettings['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
+	{
+		$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
+		$base_dir = !empty($modSettings['automanage_attachments']) ? $base_dir : 0;
+	}
+	else
+		$base_dir = 0;
+
+	// Get the last attachment directory for that base directory
+	if (empty($modSettings['last_attachments_directory'][$base_dir]))
+		$modSettings['last_attachments_directory'][$base_dir] = 0;
+	// And increment it.
+	$modSettings['last_attachments_directory'][$base_dir]++;
+
+	$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . $modSettings['last_attachments_directory'][$base_dir];
+	if (automanage_attachments_create_directory($updir))
+	{
+		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
+		updateSettings(array(
+			'last_attachments_directory' => serialize($modSettings['last_attachments_directory']),
+			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
+		));
+		$modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
+
+		return true;
+	}
+	else
+		return false;
+}
+
+function mama_get_directory_tree_elements ($directory)
+{
+	/*
+		In Windows server both \ and / can be used as directory separators in paths
+		In Linux (and presumably *nix) servers \ can be part of the name
+		So for this reasons:
+			* in Windows we need to explode for both \ and /
+			* while in linux should be safe to explode only for / (aka DIRECTORY_SEPARATOR)
+	*/
+	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
+		$tree = preg_split('#[\\\/]#', $directory);
+	else
+	{
+		if (substr($directory, 0, 1)!=DIRECTORY_SEPARATOR)
+			return false;
+
+		$tree = explode(DIRECTORY_SEPARATOR, trim($directory,DIRECTORY_SEPARATOR));
+	}
+	return $tree;
+}
+
+function mama_init_dir (&$tree, &$count)
+{
+	$directory = '';
+	// If on Windows servers the first part of the path is the drive (e.g. "C:")
+	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
+	{
+		 //Better be sure that the first part of the path is actually a drive letter...
+		 //...even if, I should check this in the admin page...isn't it?
+		 //...NHAAA Let's leave space for users' complains! :P
+		if (preg_match('/^[a-z]:$/i',$tree[0]))
+			$directory = array_shift($tree);
+		else
+			return false;
+
+		$count--;
+	}
+	return $directory;
+}
+
+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());
+}
+
+/**
+ * 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 should'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]];
+			}
+		}
+	}
+
+	// Is there room for this sucker?
+	if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
+	{
+		// Check the folder size and count. If it hasn't been done already.
+		if (empty($context['dir_size']) || empty($context['dir_files']))
+		{
+			$request = $smcFunc['db_query']('', '
+				SELECT COUNT(*), SUM(size)
+				FROM {db_prefix}attachments
+				WHERE id_folder = {int:folder_id}',
+				array(
+					'folder_id' => (empty($modSettings['currentAttachmentUploadDir']) ? 1 : $modSettings['currentAttachmentUploadDir']),
+				)
+			);
+			list ($context['dir_files'], $context['dir_size']) = $smcFunc['db_fetch_row']($request);
+			$smcFunc['db_free_result']($request);
+		}
+		$context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
+		$context['dir_files']++;
+
+		// Are we about to run out of room? Let's notify the admin then.
+		if (empty($modSettings['attachment_full_notified']) && empty($modSettings['attachmentDirSizeLimit']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024
+			|| (empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
+		{
+			require_once($sourcedir . '/Subs-Admin.php');
+			emailAdmins('admin_attachments_full');
+			updateSettings(array('attachment_full_notified' => 1));
+		}
+
+		// // No room left.... What to do now???
+		if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit']
+			|| (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
+		{
+			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
+			{
+				// Move it to the new folder if we can.
+				if (automanage_attachments_by_space())
+				{
+					rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
+					$_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
+					$_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
+					$context['dir_size'] = $_SESSION['temp_attachments'][$attachID]['size'];
+					$context['dir_files'] = 1;
+				}
+				// Or, let the user know that it ain't gonna happen.
+				else
+				{
+					if (isset($context['dir_creation_error']))
+						$_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
+					else
+						$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
+				}
+			}
+			else
+				$_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));
+		}
+	}
+
+	// Undo the math if there's an error
+	if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
+	{
+		if (isset($context['dir_size']))
+			$context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
+		if (isset($context['dir_files']))
+			$context['dir_files']--;
+		$context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
+		$context['attachments']['quantity']--;
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * 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');
+
+	// 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'
+	);
+
+	// 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]];
+	}
+
+	// Get the hash if no hash has been given yet.
+	if (empty($attachmentOptions['file_hash']))
+		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
+
+	// Assuming no-one set the extension let's take a look at it.
+	if (empty($attachmentOptions['fileext']))
+	{
+		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
+		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
+			$attachmentOptions['fileext'] = '';
+	}
+
+	$smcFunc['db_insert']('',
+		'{db_prefix}attachments',
+		array(
+			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
+			'size' => 'int', 'width' => 'int', 'height' => 'int',
+			'mime_type' => 'string-20', 'approved' => 'int',
+		),
+		array(
+			(int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
+			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
+			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
+		),
+		array('id_attach')
+	);
+	$attachmentOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
+
+	// @todo Add an error here maybe?
+	if (empty($attachmentOptions['id']))
+		return false;
+
+	// Now that we have the attach id, let's rename this sucker and finish up.
+	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['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',
+			array(
+				'id_attach' => 'int', 'id_msg' => 'int',
+			),
+			array(
+				$attachmentOptions['id'], (int) $attachmentOptions['post'],
+			),
+			array()
+		);
+
+	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
+		return true;
+
+	// Like thumbnails, do we?
+	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
+	{
+		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
+		{
+			// Figure out how big we actually made it.
+			$size = @getimagesize($attachmentOptions['destination'] . '_thumb');
+			list ($thumb_width, $thumb_height) = $size;
+
+			if (!empty($size['mime']))
+				$thumb_mime = $size['mime'];
+			elseif (isset($validImageTypes[$size[2]]))
+				$thumb_mime = 'image/' . $validImageTypes[$size[2]];
+			// Lord only knows how this happened...
+			else
+				$thumb_mime = '';
+
+			$thumb_filename = $attachmentOptions['name'] . '_thumb';
+			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
+			$thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true);
+			$thumb_path = $attachmentOptions['destination'] . '_thumb';
+
+			// We should check the file size and count here since thumbs are added to the existing totals.
+			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1 && !empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
+			{
+				$context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
+				$context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
+
+				// If the folder is full, try to create a new one and move the thumb to it.
+				if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
+				{
+					if (automanage_attachments_by_space())
+					{
+						rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
+						$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
+						$context['dir_size'] = $thumb_size;
+						$context['dir_files'] = 1;
+					}
+				}
+			}
+			// If a new folder has been already created. Gotta move this thumb there then.
+			if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
+			{
+				rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
+				$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
+			}
+
+			// To the database we go!
+			$smcFunc['db_insert']('',
+				'{db_prefix}attachments',
+				array(
+					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
+					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
+				),
+				array(
+					$modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
+					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
+				),
+				array('id_attach')
+			);
+			$attachmentOptions['thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
+
+			if (!empty($attachmentOptions['thumb']))
+			{
+				$smcFunc['db_query']('', '
+					UPDATE {db_prefix}attachments
+					SET id_thumb = {int:id_thumb}
+					WHERE id_attach = {int:id_attach}',
+					array(
+						'id_thumb' => $attachmentOptions['thumb'],
+						'id_attach' => $attachmentOptions['id'],
+					)
+				);
+
+				rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
+			}
+		}
+	}
+	return true;
+}
+
+?>

+ 11 - 0
Sources/Load.php

@@ -175,6 +175,17 @@ function reloadSettings()
 	// Is post moderation alive and well?
 	$modSettings['postmod_active'] = isset($modSettings['admin_features']) ? in_array('pm', explode(',', $modSettings['admin_features'])) : true;
 
+	// Here to justify the name of this function. :P
+	// It should be added to the install and upgrade scripts.
+	// But since the convertors need to be updated also. This is easier.
+	if (empty($modSettings['currentAttachmentUploadDir']))
+	{
+		updateSettings(array(
+			'attachmentUploadDir' => serialize(array(1 => $modSettings['attachmentUploadDir'])),
+			'currentAttachmentUploadDir' => 1,
+		));
+	}
+
 	// Integration is cool.
 	if (defined('SMF_INTEGRATION_SETTINGS'))
 	{

+ 462 - 74
Sources/ManageAttachments.php

@@ -86,14 +86,40 @@ function ManageAttachments()
 
 function ManageAttachmentSettings($return_config = false)
 {
-	global $txt, $modSettings, $scripturl, $context, $options, $sourcedir;
+	global $txt, $modSettings, $scripturl, $context, $options, $sourcedir, $boarddir;
 
-	$context['valid_upload_dir'] = is_dir($modSettings['attachmentUploadDir']) && is_writable($modSettings['attachmentUploadDir']);
+	require_once($sourcedir . '/Attachments.php');
+
+	// Get the current attachment directory.
+	$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+	$context['attachmentUploadDir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
+
+	// If not set, show a default path for the base directory
+	if (!isset($_GET['save']) && empty($modSettings['basedirectory_for_attachments']))
+		if (!empty($modSettings['currentAttachmentUploadDir']))
+			$modSettings['basedirectory_for_attachments'] = $modSettings['attachmentUploadDir'][1];
+		else
+			$modSettings['basedirectory_for_attachments'] = $context['attachmentUploadDir'];
+
+	$context['valid_upload_dir'] = is_dir($context['attachmentUploadDir']) && is_writable($context['attachmentUploadDir']);
+
+	if (!empty($modSettings['automanage_attachments']))
+		$context['valid_basedirectory'] =  !empty($modSettings['basedirectory_for_attachments']) && is_writable($modSettings['basedirectory_for_attachments']);
+	else
+		$context['valid_basedirectory'] = true;
+
+	// A bit of razzle dazzle with the $txt strings. :)
+	$txt['attachment_path'] = $context['attachmentUploadDir'];
+	$txt['basedirectory_for_attachments_path']= isset($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
+	$txt['use_subdirectories_for_attachments_note'] = empty($modSettings['attachment_basedirectories']) || empty($modSettings['use_subdirectories_for_attachments']) ? $txt['use_subdirectories_for_attachments_note'] : '';
+	$txt['attachmentUploadDir_multiple_configure'] = '<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">[' . $txt['attachmentUploadDir_multiple_configure'] . ']</a>';
+	$txt['attach_current_dir'] = empty($modSettings['automanage_attachments']) ? $txt['attach_current_dir'] : $txt['attach_last_dir'];
+	$txt['attach_current_dir_warning'] = $txt['attach_current_dir'] . $txt['attach_current_dir_warning'];
+	$txt['basedirectory_for_attachments_warning'] = $txt['basedirectory_for_attachments_current'] . $txt['basedirectory_for_attachments_warning'];
 
 	// Perform a test to see if the GD module or ImageMagick are installed.
 	$testImg = get_extension_funcs('gd') || class_exists('Imagick');
-	$txt['attachmentUploadDir_multiple_configure'] = '<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">[' . $txt['attachmentUploadDir_multiple_configure'] . ']</a>';
-	
+
 	// See if we can find if the server is set up to support the attacment limits
 	$post_max_size = ini_get('post_max_size');
 	$upload_max_filesize = ini_get('upload_max_filesize');
@@ -109,8 +135,14 @@ function ManageAttachmentSettings($return_config = false)
 			array('check', 'attachmentRecodeLineEndings'),
 		'',
 			// Directory and size limits.
-			empty($modSettings['currentAttachmentUploadDir']) ? array('text', 'attachmentUploadDir', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 40, 'invalid' => !$context['valid_upload_dir']) : array('var_message', 'attachmentUploadDir_multiple', 'message' => 'attachmentUploadDir_multiple_configure'),
+			array('select', 'automanage_attachments', array(0 => $txt['attachments_normal'], 1 => $txt['attachments_auto_space'], 2 => $txt['attachments_auto_years'], 3 => $txt['attachments_auto_months'], 4 => $txt['attachments_auto_16'])),
+			array('check', 'use_subdirectories_for_attachments', 'subtext' => $txt['use_subdirectories_for_attachments_note']),
+			(empty($modSettings['attachment_basedirectories']) ? array('text', 'basedirectory_for_attachments', 40,) : array('var_message', 'basedirectory_for_attachments', 'message' => 'basedirectory_for_attachments_path', 'invalid' => empty($context['valid_basedirectory']), 'text_label' => (!empty($context['valid_basedirectory']) ? $txt['basedirectory_for_attachments_current'] : $txt['basedirectory_for_attachments_warning']))),
+			array('var_message', 'attach_current_directory', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 'message' => 'attachment_path', 'invalid' => empty($context['valid_upload_dir']), 'text_label' => (!empty($context['valid_upload_dir']) ? $txt['attach_current_dir'] : $txt['attach_current_dir_warning'])),
+			array('int', 'attachmentDirFileLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
 			array('int', 'attachmentDirSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
+		'',
+			// Posting limits
 			array('int', 'attachmentPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
 			array('warning', empty($testPM) ? 'attachment_postsize_warning' : ''),
 			array('int', 'attachmentSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
@@ -137,8 +169,21 @@ function ManageAttachmentSettings($return_config = false)
 			array('warning', 'attachment_thumb_memory_note'),
 			array('text', 'attachmentThumbWidth', 6),
 			array('text', 'attachmentThumbHeight', 6),
+		'',
+			array('int', 'max_image_width', 'subtext' => $txt['zero_for_no_limit']),
+			array('int', 'max_image_height', 'subtext' => $txt['zero_for_no_limit']),
 	);
 
+	$context['settings_post_javascript'] = '
+	var storing_type = document.getElementById(\'automanage_attachments\');
+	var base_dir = document.getElementById(\'use_subdirectories_for_attachments\');
+
+	createEventListener(storing_type)
+	storing_type.addEventListener("change", toggleSubDir, false);
+	createEventListener(base_dir)
+	base_dir.addEventListener("change", toggleSubDir, false);
+	toggleSubDir();';
+
 	call_integration_hook('integrate_modify_attachment_settings', array(&$config_vars));
 
 	if ($return_config)
@@ -153,6 +198,38 @@ function ManageAttachmentSettings($return_config = false)
 	{
 		checkSession();
 
+		if (!empty($_POST['use_subdirectories_for_attachments']))
+		{
+			if(isset($_POST['use_subdirectories_for_attachments']) && empty($_POST['basedirectory_for_attachments']))
+				$_POST['basedirectory_for_attachments'] = (!empty($modSettings['basedirectory_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
+
+			if (!empty($_POST['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
+			{
+				if (!is_array($modSettings['attachment_basedirectories']))
+					$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
+			}
+			else
+				$modSettings['attachment_basedirectories'] = array();
+
+			if (!empty($_POST['use_subdirectories_for_attachments']) && !empty($_POST['basedirectory_for_attachments']) && !in_array($_POST['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']))
+			{
+				$currentAttachmentUploadDir = $modSettings['currentAttachmentUploadDir'];
+
+				if (!in_array($_POST['basedirectory_for_attachments'], $modSettings['attachmentUploadDir']))
+					if (!automanage_attachments_create_directory($_POST['basedirectory_for_attachments']))
+						$_POST['basedirectory_for_attachments'] = $modSettings['basedirectory_for_attachments'];				}
+
+				if (!in_array($_POST['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']))
+				{
+					$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $_POST['basedirectory_for_attachments'];
+					updateSettings(array(
+						'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
+						'currentAttachmentUploadDir' => $currentAttachmentUploadDir,
+					));
+				}
+			}
+		}
+
 		call_integration_hook('integrate_save_attachment_settings');
 
 		saveDBSettings($config_vars);
@@ -1458,18 +1535,10 @@ function RepairAttachments()
 	// What about files who are not recorded in the database?
 	if ($_GET['step'] <= 5)
 	{
-		if (!empty($modSettings['currentAttachmentUploadDir']))
-		{
-			if (!is_array($modSettings['attachmentUploadDir']))
-				$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
-
-			// Just use the current path for temp files.
-			$attach_dirs = $modSettings['attachmentUploadDir'];
-		}
-		else
-		{
-			$attach_dirs = array($modSettings['attachmentUploadDir']);
-		}
+		// Just use the current path for temp files.
+		if (!is_array($modSettings['attachmentUploadDir']))
+			$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+		$attach_dirs = $modSettings['attachmentUploadDir'];
 
 		$current_check = 0;
 		$max_checks = 500;
@@ -1770,13 +1839,24 @@ function ApproveAttachments($attachments)
  */
 function ManageAttachmentPaths()
 {
-	global $modSettings, $scripturl, $context, $txt, $sourcedir, $smcFunc;
+	global $modSettings, $scripturl, $context, $txt, $sourcedir, $boarddir, $smcFunc;
+
+	// Since this needs to be done eventually.
+	if (!is_array($modSettings['attachmentUploadDir']))
+		$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+	if (!isset($modSettings['attachment_basedirectories']))
+		$modSettings['attachment_basedirectories'] = array();
+	elseif (!is_array($modSettings['attachment_basedirectories']))
+		$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
+
+	$errors = array();
 
 	// Saving?
 	if (isset($_REQUEST['save']))
 	{
 		checkSession();
 
+		$_POST['current_dir'] = (int) $_POST['current_dir'];
 		$new_dirs = array();
 		foreach ($_POST['dirs'] as $id => $path)
 		{
@@ -1784,33 +1864,132 @@ function ManageAttachmentPaths()
 			if ($id < 1)
 				continue;
 
+			// Hmm, a new path maybe?
+			if (!array_key_exists($id, $modSettings['attachmentUploadDir']))
+			{
+				// or is it?
+				if (in_array($path, $modSettings['attachmentUploadDir']) || in_array($boarddir . DIRECTORY_SEPARATOR . $path, $modSettings['attachmentUploadDir']))
+				{
+						$errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg'];
+						continue;
+				}
+
+				// OK, so let's try to create it then.
+				require_once($sourcedir . '/Attachments.php');
+				if (automanage_attachments_create_directory($path))
+					$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
+				else
+					$errors[] =  $path . ': ' . $txt[$context['dir_creation_error']];
+			}
+
+			// Changing a directory name?
+			if (!empty($modSettings['attachmentUploadDir'][$id]) && !empty($path) && $path != $modSettings['attachmentUploadDir'][$id])
+			{
+				if ($path != $modSettings['attachmentUploadDir'][$id] && !is_dir($path))
+				{
+					if (!@rename($modSettings['attachmentUploadDir'][$id], $path))
+					{
+						$errors[] = $path . ': ' . $txt['attach_dir_no_rename'];
+						$path = $modSettings['attachmentUploadDir'][$id];
+					}
+				}
+				else
+				{
+					$errors[] = $path . ': ' . $txt['attach_dir_exists_msg'];
+					$path = $modSettings['attachmentUploadDir'][$id];
+				}
+
+				// Update the base directory path
+				if (!empty($modSettings['attachment_basedirectories']) && array_key_exists($id, $modSettings['attachment_basedirectories']))
+				{
+					$modSettings['attachment_basedirectories'][$id] = $path;
+					$update = array('attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']));
+				}
+			}
+
 			if (empty($path))
 			{
-				// Let's not try to delete a path with files in it.
-				$request = $smcFunc['db_query']('', '
-					SELECT COUNT(id_attach) AS num_attach
-					FROM {db_prefix}attachments
-					WHERE id_folder = {int:id_folder}',
-					array(
-						'id_folder' => (int) $id,
-					)
-				);
+				$path = $modSettings['attachmentUploadDir'][$id];
+
+				// It's not a good idea to delete the current directory. 
+				if ($id == (!empty($_POST['current_dir']) ? $_POST['current_dir'] : $modSettings['currentAttachmentUploadDir']))
+					$errors[] = $path . ': ' . $txt['attach_dir_is_current'];
+				// Or the current base directory
+				elseif (!empty($modSettings['basedirectory_for_attachments']) && $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id])
+					$errors[] = $path . ': ' . $txt['attach_dir_is_current_bd'];
+				else
+				{
+					// Let's not try to delete a path with files in it.
+					$request = $smcFunc['db_query']('', '
+						SELECT COUNT(id_attach) AS num_attach
+						FROM {db_prefix}attachments
+						WHERE id_folder = {int:id_folder}',
+						array(
+							'id_folder' => (int) $id,
+						)
+					);
 
-				list ($num_attach) = $smcFunc['db_fetch_row']($request);
-				$smcFunc['db_free_result']($request);
+					list ($num_attach) = $smcFunc['db_fetch_row']($request);
+					$smcFunc['db_free_result']($request);
 
-				// It's safe to delete.
-				if ($num_attach == 0)
-					continue;
+					// A check to see if it's a used base dir.
+					if (!empty($modSettings['attachment_basedirectories']))
+					{
+						// Count any sub-folders.
+						foreach ($modSettings['attachmentUploadDir'] as $sub)
+							if (strpos($sub, $path . DIRECTORY_SEPARATOR) !== false)
+								$num_attach++;
+					}
+
+					// It's safe to delete. So try to delete the folder also
+					if ($num_attach == 0)
+					{
+						if (is_dir($path))
+							$doit = true;
+						elseif (is_dir($boarddir . DIRECTORY_SEPARATOR . $path))
+						{
+							$doit = true;
+							$path = $boarddir . DIRECTORY_SEPARATOR . $path;
+						}
+
+						if (isset($doit))
+						{
+							unlink($path . '/.htaccess');
+							unlink($path . '/index.php');
+							if (!@rmdir($path))
+								$errors[] = $path . ': ' . $txt['attach_dir_no_delete'];
+						}
+					}
+					else
+						$errors[] = $path . ': ' . $txt['attach_dir_no_remove'];
+
+					// Remove it from the base directory list.
+					if (empty($errors) && !empty($modSettings['attachment_basedirectories']))
+					{
+						unset($modSettings['attachment_basedirectories'][$id]);
+						updateSettings(array('attachment_basedirectories' => serialize($modSettings['attachment_basedirectories'])));
+					}
+
+					if (empty($errors))
+						continue;
+				}
 			}
 
 			$new_dirs[$id] = $path;
 		}
 
 		// We need to make sure the current directory is right.
-		$_POST['current_dir'] = (int) $_POST['current_dir'];
+		if (empty($_POST['current_dir']) && !empty($modSettings['currentAttachmentUploadDir']))
+			$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
+
+		// Find the current directory if there's no value carried,
 		if (empty($_POST['current_dir']) || empty($new_dirs[$_POST['current_dir']]))
-			fatal_lang_error('attach_path_current_bad', false);
+		{
+			if (in_array($modSettings['currentAttachmentUploadDir'], $modSettings['attachmentUploadDir']))
+				$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
+			else
+				$_POST['current_dir'] = max(array_keys($modSettings['attachmentUploadDir']));
+		}
 
 		// Going back to just one path?
 		if (count($new_dirs) == 1)
@@ -1829,31 +2008,88 @@ function ManageAttachmentPaths()
 						)
 					);
 
-				updateSettings(array(
-					'currentAttachmentUploadDir' => 0,
-					'attachmentUploadDir' => $dir,
-				));
+				$update = array(
+					'currentAttachmentUploadDir' => 1,
+					'attachmentUploadDir' => serialize(array(1 => $dir)),
+				);
 			}
 		}
 		else
+		{
 			// Save it to the database.
-			updateSettings(array(
+			$update = array(
 				'currentAttachmentUploadDir' => $_POST['current_dir'],
 				'attachmentUploadDir' => serialize($new_dirs),
+			);
+		}
+
+		if (!empty($update))
+			updateSettings($update);
+
+		if (!empty($errors))
+			$_SESSION['errors']['dir'] = $errors;
+
+		redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
+	}
+
+	// Saving a base directory?
+	if (isset($_REQUEST['save2']))
+	{
+		checkSession();
+
+		// Changing the current base directory?
+		$_POST['current_base_dir'] = (int) $_POST['current_base_dir'];
+		if (empty($_POST['new_base_dir']) && !empty($_POST['current_base_dir']))
+		{
+			if ($modSettings['basedirectory_for_attachments'] != $modSettings['attachmentUploadDir'][$_POST['current_base_dir']])
+				$update = (array(
+					'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
+				));
+
+			$modSettings['attachmentUploadDir'] = serialize($modSettings['attachmentUploadDir']);
+		}
+
+		// Or adding a new one?
+		if (!empty($_POST['new_base_dir']))
+		{
+			require_once($sourcedir . '/Attachments.php');
+			$_POST['new_base_dir'] = htmlspecialchars($_POST['new_base_dir'], ENT_QUOTES);
+
+			$current_dir = $modSettings['currentAttachmentUploadDir'];
+
+			if (!in_array($_POST['new_base_dir'], $modSettings['attachmentUploadDir']))
+				if (!automanage_attachments_create_directory($_POST['new_base_dir']))
+					$errors[] = $_POST['new_base_dir'] . ': ' . $txt['attach_dir_base_no_create'];
+			else
+				$errors[] = $_POST['new_base_dir'] . ': ' . $txt['attach_dir_base_dupe_msg'];
+
+
+			$modSettings['currentAttachmentUploadDir'] = array_search($_POST['new_base_dir'], $modSettings['attachmentUploadDir']);
+			if (!in_array($_POST['new_base_dir'], $modSettings['attachment_basedirectories']))
+				$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $_POST['new_base_dir'];
+			ksort($modSettings['attachment_basedirectories']);
+
+			$update = (array(
+				'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
+				'basedirectory_for_attachments' => $_POST['new_base_dir'],
+				'currentAttachmentUploadDir' => $current_dir,
 			));
+		}
+
+		if (!empty($errors))
+			$_SESSION['base'] = $errors;
+
+		if (!empty($update))
+			updateSettings($update);
+
+		redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
 	}
 
-	// Are they here for the first time?
-	if (empty($modSettings['currentAttachmentUploadDir']))
+	if (isset($_SESSION['errors']))
 	{
-		$modSettings['attachmentUploadDir'] = array(
-			1 => $modSettings['attachmentUploadDir']
-		);
-		$modSettings['currentAttachmentUploadDir'] = 1;
+		$errors = $_SESSION['errors'];
+		unset($_SESSION['errors']);
 	}
-	// Otherwise just load up their attachment paths.
-	else
-		$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
 
 	$listOptions = array(
 		'id' => 'attach_paths',
@@ -1865,13 +2101,13 @@ function ManageAttachmentPaths()
 		'columns' => array(
 			'current_dir' => array(
 				'header' => array(
-					'value' => $txt['attach_current_dir'],
+					'value' => $txt['attach_current'],
 				),
 				'data' => array(
 					'function' => create_function('$rowData', '
-						return \'<input type="radio" name="current_dir" value="\' . $rowData[\'id\'] . \'" \' . ($rowData[\'current\'] ? \'checked="checked"\' : \'\') . \' class="input_radio" />\';
+						return \'<input type="radio" name="current_dir" value="\' . $rowData[\'id\'] . \'" \' . ($rowData[\'current\'] ? \' checked="checked"\' : \'\') . (!empty($rowData[\'disable_current\']) ? \' disabled="disabled"\' : \'\') . \' class="input_radio" />\';
 					'),
-					'style' => 'text-align: center; width: 15%;',
+					'style' => 'text-align: center; width: 10%;',
 				),
 			),
 			'path' => array(
@@ -1880,9 +2116,9 @@ function ManageAttachmentPaths()
 				),
 				'data' => array(
 					'function' => create_function('$rowData', '
-						return \'<input type="text" size="30" name="dirs[\' . $rowData[\'id\'] . \']" value="\' . $rowData[\'path\'] . \'" class="input_text" style="width: 100%" />\';
+						return \'<input type="hidden" name="dirs[\' . $rowData[\'id\'] . \']" value="\' . $rowData[\'path\'] . \'" /><input type="text" size="40" name="dirs[\' . $rowData[\'id\'] . \']" value="\' . $rowData[\'path\'] . \'"\' . (!empty($rowData[\'disable_base_dir\']) ? \' disabled="disabled"\' : \'\') . \' class="input_text" style="width: 100%" />\';
 					'),
-					'style' => 'text-align: center; width: 30%;',
+					'style' => 'text-align: center; width: 40%;',
 				),
 			),
 			'current_size' => array(
@@ -1922,14 +2158,98 @@ function ManageAttachmentPaths()
 				'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /><input type="submit" name="new_path" value="' . $txt['attach_add_path'] . '" class="button_submit" />&nbsp;<input type="submit" name="save" value="' . $txt['save'] . '" class="button_submit" />',
 				'style' => 'text-align: right;',
 			),
+			empty($errors['dir']) ? array(
+				'position' => 'top_of_list',
+				'value' => $txt['attach_dir_desc'],
+				'style' => 'text-align: left; padding: 5px 10px',
+				'class' => 'windowbg2 smalltext'
+			) : array(
+				'position' => 'top_of_list',
+				'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['dir']),
+				'style' => 'text-align: left;',
+				'class' => 'noticebox',
+			),
 		),
 	);
-
 	require_once($sourcedir . '/Subs-List.php');
 	createList($listOptions);
 
+	if (!empty($modSettings['attachment_basedirectories']))
+	{
+		$listOptions2 = array(
+			'id' => 'base_paths',
+			'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
+			'title' => $txt['attach_base_paths'],
+			'get_items' => array(
+				'function' => 'list_getBaseDirs',
+			),
+			'columns' => array(
+				'current_dir' => array(
+					'header' => array(
+						'value' => $txt['attach_current'],
+					),
+					'data' => array(
+						'function' => create_function('$rowData', '
+							return \'<input type="radio" name="current_base_dir" value="\' . $rowData[\'id\'] . \'" \' . ($rowData[\'current\'] ? \' checked="checked"\' : \'\') . \' class="input_radio" />\';
+						'),
+						'style' => 'text-align: center; width: 10%;',
+					),
+				),
+				'path' => array(
+					'header' => array(
+						'value' => $txt['attach_path'],
+					),
+					'data' => array(
+						'db' => 'path',
+						'style' => 'width: 45%;',
+					),
+				),
+				'num_dirs' => array(
+					'header' => array(
+						'value' => $txt['attach_num_dirs'],
+					),
+					'data' => array(
+						'db' => 'num_dirs',
+						'style' => 'text-align: center; width: 15%;',
+					),
+				),
+				'status' => array(
+					'header' => array(
+						'value' => $txt['attach_dir_status'],
+					),
+					'data' => array(
+						'db' => 'status',
+						'style' => 'text-align: center; width: 15%;',
+					),
+				),
+			),
+			'form' => array(
+				'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
+			),
+			'additional_rows' => array(
+				array(
+					'position' => 'below_table_data',
+					'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /><input type="submit" name="new_base_path" value="' . $txt['attach_add_path'] . '" class="button_submit" />&nbsp;<input type="submit" name="save2" value="' . $txt['save'] . '" class="button_submit" />',
+					'style' => 'text-align: right;',
+				),
+				empty($errors['base']) ? array(
+					'position' => 'top_of_list',
+					'value' => $txt['attach_dir_base_desc'],
+					'style' => 'text-align: left; padding: 5px 10px',
+					'class' => 'windowbg2 smalltext'
+				) : array(
+					'position' => 'top_of_list',
+					'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['base']),
+					'style' => 'text-align: left;',
+					'class' => 'noticebox',
+				),
+			),
+		);
+		createList($listOptions2);
+	}
+
 	// Fix up our template.
-	$context[$context['admin_menu_name']]['current_subsection'] = 'attachments';
+	$context[$context['admin_menu_name']]['current_subsection'] = 'attachpaths';
 	$context['page_title'] = $txt['attach_path_manage'];
 	$context['sub_template'] = 'attachment_paths';
 }
@@ -1941,23 +2261,27 @@ function list_getAttachDirs()
 {
 	global $smcFunc, $modSettings, $context, $txt;
 
-	// The dirs should already have been unserialized but just in case...
-	if (!is_array($modSettings['attachmentUploadDir']))
-		$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
+	if (isset($_SESSION['dir_errors']))
+	{
+		$context['dir_errors'] = $_SESSION['dir_errors'];
+		unset($_SESSION['dir_errors']);
+	}
 
 	$request = $smcFunc['db_query']('', '
-		SELECT id_folder, COUNT(id_attach) AS num_attach
-		FROM {db_prefix}attachments' . (empty($modSettings['custom_avatar_enabled']) ? '' : '
-		WHERE attachment_type != {int:type_avatar}') . '
+		SELECT id_folder, COUNT(id_attach) AS num_attach, SUM(size) AS size_attach
+		FROM {db_prefix}attachments
 		GROUP BY id_folder',
 		array(
-			'type_avatar' => 1,
 		)
 	);
 
 	$expected_files = array();
+	$expected_size = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
+	{
 		$expected_files[$row['id_folder']] = $row['num_attach'];
+		$expected_size[$row['id_folder']] = $row['size_attach'];
+	}
 	$smcFunc['db_free_result']($request);
 
 	$attachdirs = array();
@@ -1968,15 +2292,33 @@ function list_getAttachDirs()
 			$expected_files[$id] = 0;
 
 		// Check if the directory is doing okay.
-		list ($status, $error, $size) = attachDirStatus($dir, $expected_files[$id]);
+		list ($status, $error, $files) = attachDirStatus($dir, $expected_files[$id]);
+
+		// If it is one, let's show that it's a base directory.
+		$sub_dirs = 0;
+		$is_base_dir = false;
+		if (!empty($modSettings['attachment_basedirectories']))
+		{
+			$is_base_dir = in_array($dir, $modSettings['attachment_basedirectories']);
+
+			// Count any sub-folders.
+			foreach ($modSettings['attachmentUploadDir'] as $sid => $sub)
+				if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
+				{
+					$expected_files[$id]++;
+					$sub_dirs++;
+				}
+		}
 
 		$attachdirs[] = array(
 			'id' => $id,
 			'current' => $id == $modSettings['currentAttachmentUploadDir'],
+			'disable_current' => isset($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] > 0,
+			'disable_base_dir' =>  $is_base_dir && $sub_dirs > 0 && !empty($files) && empty($error) && empty($save_errors),
 			'path' => $dir,
-			'current_size' => $size,
-			'num_files' => $expected_files[$id],
-			'status' => ($error ? '<span class="error">' : '') . sprintf($txt['attach_dir_' . $status], $context['session_id'], $context['session_var']) . ($error ? '</span>' : ''),
+			'current_size' => !empty($expected_size[$id]) ? comma_format($expected_size[$id] / 1024, 0) : 0,
+			'num_files' => comma_format($expected_files[$id] - $sub_dirs, 0) . ($sub_dirs > 0 ? ' (' . $sub_dirs . ')' : ''),
+			'status' => ($is_base_dir ? $txt['attach_dir_basedir'] . '<br />' : '') . ($error ? '<div class="error">' : '') . sprintf($txt['attach_dir_' . $status], $context['session_id'], $context['session_var']) . ($error ? '</div>' : ''),
 		);
 	}
 
@@ -1994,23 +2336,72 @@ function list_getAttachDirs()
 	return $attachdirs;
 }
 
+/**
+ * Prepare the base directories to be displayed in a list.
+ */
+function list_getBaseDirs()
+{
+	global $modSettings, $context, $txt;
+
+	if (empty($modSettings['attachment_basedirectories']))
+		return;
+
+	$basedirs = array();
+	// Get a list of the base directories.
+	foreach ($modSettings['attachment_basedirectories'] as $id => $dir)
+	{
+		// Loop through the attach directory array to count any sub-directories
+		$expected_dirs = 0;
+		foreach ($modSettings['attachmentUploadDir'] as $sid => $sub)
+			if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
+				$expected_dirs++;
+
+		if (!is_dir($dir))
+			$status = 'does_not_exist';
+		elseif (!is_writeable($dir))
+			$status = 'not_writable';
+		else
+			$status = 'ok';
+
+		$basedirs[] = array(
+			'id' => $id,
+			'current' => $dir == $modSettings['basedirectory_for_attachments'],
+			'path' => $dir,
+			'num_dirs' => $expected_dirs,
+			'status' => $status == 'ok' ? $txt['attach_dir_ok'] : ('<span class="error">' . $txt['attach_dir_' . $status] . '</span>'),
+		);
+	}
+
+	if (isset($_REQUEST['new_base_path']))
+		$basedirs[] = array(
+			'id' => '',
+			'current' => false,
+			'path' => '<input type="text" name="new_base_dir" value="" size="40" />',
+			'num_dirs' => '',
+			'status' => '',
+		);
+
+	return $basedirs;
+}
+
 /**
  * Checks the status of an attachment directory and returns an array
  *  of the status key, if that status key signifies an error, and
- *  the folder size.
+ *  the file count.
  *
  * @param string $dir
  * @param int $expected_files
  */
 function attachDirStatus($dir, $expected_files)
 {
+	global $sourcedir, $context;
+
 	if (!is_dir($dir))
 		return array('does_not_exist', true, '');
 	elseif (!is_writable($dir))
 		return array('not_writable', true, '');
 
 	// Everything is okay so far, start to scan through the directory.
-	$dir_size = 0;
 	$num_files = 0;
 	$dir_handle = dir($dir);
 	while ($file = $dir_handle->read())
@@ -2019,21 +2410,18 @@ function attachDirStatus($dir, $expected_files)
 		if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
 			continue;
 
-		$dir_size += filesize($dir . '/' . $file);
 		$num_files++;
 	}
 	$dir_handle->close();
 
-	$dir_size = round($dir_size / 1024, 2);
-
 	if ($num_files < $expected_files)
-		return array('files_missing', true, $dir_size);
+		return array('files_missing', true, $num_files);
 	// Empty?
 	elseif ($expected_files == 0)
-		return array('unused', false, $dir_size);
+		return array('unused', false, $num_files);
 	// All good!
 	else
-		return array('ok', false, $dir_size);
+		return array('ok', false, $num_files);
 }
 
 ?>

+ 4 - 22
Sources/ManageServer.php

@@ -361,28 +361,9 @@ function ModifyCacheSettings($return_config = false)
 	// some javascript to enable / disable certain settings if the option is not selected
 	$context['settings_post_javascript'] = '
 		var cache_type = document.getElementById(\'cache_accelerator\');
-		mod_addEvent(cache_type, \'change\', toggleCache);
-		toggleCache();
-
-		function mod_addEvent(control, ev, fn)
-		{
-			if (control.addEventListener)
-			{
-				control.addEventListener(ev, fn, false);
-			}
-			else if (control.attachEvent)
-			{
-				control.attachEvent(\'on\'+ev, fn);
-			}
-		}
-		function toggleCache()
-		{
-			var select_elem1 = document.getElementById(\'cache_memcached\');
-			var select_elem2 = document.getElementById(\'cachedir\');
-			select_elem1.disabled = cache_type.value != "memcached";
-			select_elem2.disabled = cache_type.value != "smf";
-		}
-	';
+		createEventListener(cache_type);
+		cache_type.addEventListener("change", toggleCache);
+		toggleCache();';
 
 	call_integration_hook('integrate_modify_cache_settings', array(&$config_vars));
 
@@ -956,6 +937,7 @@ function ShowPHPinfoSettings()
 	$info_lines = preg_replace('~^.*<body>(.*)</body>.*$~', '$1', ob_get_contents());
 	$info_lines = explode("\n", strip_tags($info_lines, "<tr><td><h2>"));
 	ob_end_clean();
+
 	// remove things that could be considered sensitive
 	$remove = '_COOKIE|Cookie|_GET|_REQUEST|REQUEST_URI|QUERY_STRING|REQUEST_URL|HTTP_REFERER';
 

+ 2 - 2
Sources/ManageSettings.php

@@ -644,8 +644,8 @@ function ModifyLayoutSettings($return_config = false)
 			array('check', 'enableVBStyleLogin'),
 		'',
 			// Automagic image resizing.
-			array('int', 'max_image_width'),
-			array('int', 'max_image_height'),
+			array('int', 'max_image_width', 'subtext' => $txt['zero_for_no_limit']),
+			array('int', 'max_image_height', 'subtext' => $txt['zero_for_no_limit']),
 		'',
 			// This is like debugging sorta.
 			array('check', 'timeLoadPageEnable'),

+ 7 - 199
Sources/Post.php

@@ -803,17 +803,6 @@ function Post($post_errors = 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']))
-				$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
-
-			// Just use the current path for temp files.
-			$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']))
@@ -1254,189 +1243,9 @@ function Post2()
 	$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'] = '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);
-		}
+		 require_once($sourcedir . '/Attachments.php');
+		 processAttachments();
 	}
-	// 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');
 
 	// If this isn't a new topic load the topic info that we need.
 	if (!empty($topic))
@@ -1881,6 +1690,7 @@ function Post2()
 				'tmp_name' => $attachment['tmp_name'],
 				'size' => isset($attachment['size']) ? $attachment['size'] : 0,
 				'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
+				'id_folder' => $attachment['id_folder'],
 				'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
 				'errors' => $attachment['errors'],
 			);
@@ -1894,17 +1704,14 @@ function Post2()
 						$attachIDs[] = $attachmentOptions['thumb'];
 				}
 			}
+			else
+				$attach_errors[] = '<dt>&nbsp;</dt>';
 
 			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');
+				$log_these = array('attachments_no_create', 'attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file');
 				foreach ($attachmentOptions['errors'] as $error)
 				{
 					if (!is_array($error))
@@ -2210,6 +2017,7 @@ function Post2()
 		$context['error_message'] = '<dl>';
 		$context['error_message'] .= implode("\n", $attach_errors);
 		$context['error_message'] .= '</dl>';
+		$context['error_title'] = $txt['attach_error_title'];
 
 		$context['linktree'][] = array(
 			'url' => $scripturl . '?topic=' . $topic . '.0',

+ 0 - 302
Sources/Subs-Post.php

@@ -2093,308 +2093,6 @@ function createPost(&$msgOptions, &$topicOptions, &$posterOptions)
 	return true;
 }
 
-/**
- * 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');
-
-	if (!empty($modSettings['currentAttachmentUploadDir']))
-		$id_folder = $modSettings['currentAttachmentUploadDir'];
-	else
-		$id_folder = 1;
-
-	// 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'
-	);
-
-	// 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]];
-	}
-
-	// Get the hash if no hash has been given yet.
-	if (empty($attachmentOptions['file_hash']))
-		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
-
-	// Assuming no-one set the extension let's take a look at it.
-	if (empty($attachmentOptions['fileext']))
-	{
-		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
-		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
-			$attachmentOptions['fileext'] = '';
-	}
-
-	$smcFunc['db_insert']('',
-		'{db_prefix}attachments',
-		array(
-			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
-			'size' => 'int', 'width' => 'int', 'height' => 'int',
-			'mime_type' => 'string-20', 'approved' => 'int',
-		),
-		array(
-			$id_folder, (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
-			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
-			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
-		),
-		array('id_attach')
-	);
-	$attachmentOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
-
-	// @todo Add an error here maybe?
-	if (empty($attachmentOptions['id']))
-		return false;
-
-	// 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',
-			array(
-				'id_attach' => 'int', 'id_msg' => 'int',
-			),
-			array(
-				$attachmentOptions['id'], (int) $attachmentOptions['post'],
-			),
-			array()
-		);
-
-	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
-		return true;
-
-	// Like thumbnails, do we?
-	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
-	{
-		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
-		{
-			// Figure out how big we actually made it.
-			$size = @getimagesize($attachmentOptions['destination'] . '_thumb');
-			list ($thumb_width, $thumb_height) = $size;
-
-			if (!empty($size['mime']))
-				$thumb_mime = $size['mime'];
-			elseif (isset($validImageTypes[$size[2]]))
-				$thumb_mime = 'image/' . $validImageTypes[$size[2]];
-			// Lord only knows how this happened...
-			else
-				$thumb_mime = '';
-
-			$thumb_filename = $attachmentOptions['name'] . '_thumb';
-			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
-			$thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true);
-
-			// To the database we go!
-			$smcFunc['db_insert']('',
-				'{db_prefix}attachments',
-				array(
-					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
-					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
-				),
-				array(
-					$id_folder, (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
-					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
-				),
-				array('id_attach')
-			);
-			$attachmentOptions['thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
-
-			if (!empty($attachmentOptions['thumb']))
-			{
-				$smcFunc['db_query']('', '
-					UPDATE {db_prefix}attachments
-					SET id_thumb = {int:id_thumb}
-					WHERE id_attach = {int:id_attach}',
-					array(
-						'id_thumb' => $attachmentOptions['thumb'],
-						'id_attach' => $attachmentOptions['id'],
-					)
-				);
-
-				rename($attachmentOptions['destination'] . '_thumb', getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $id_folder, false, $thumb_file_hash));
-			}
-		}
-	}
-	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
-				WHERE id_folder = {int:folder_id}',
-				array(
-					'folder_id' => empty($modSettings['currentAttachmentUploadDir']) ? 1 : $modSettings['currentAttachmentUploadDir'],
-				)
-			);
-			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;
-}
-
 /**
  * Modifying a post...
  *

+ 5 - 6
Themes/default/Errors.template.php

@@ -216,18 +216,17 @@ function template_attachment_errors()
 	<div>
 		<div class="cat_bar">
 			<h3 class="catbg">
-				', $txt['attach_error_title'], '
+				', $context['error_title'], '
 			</h3>
 		</div>
 		<div class="windowbg">
 			<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>
+				</div>',
+				!empty($context['back_link']) ? ('<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['continue'], '</a>
 				<br class="clear_right" />
 			</div>
 		</div>

+ 8 - 0
Themes/default/ManageAttachments.template.php

@@ -202,6 +202,14 @@ function template_attachment_repair()
 
 function template_attachment_paths()
 {
+	global $modSettings;
+
+	if (!empty($modSettings['attachment_basedirectories']))
+	{
+		template_show_list('base_paths');
+		echo '<br />';
+	}
+
 	template_show_list('attach_paths');
 }
 

+ 1 - 1
Themes/default/Post.template.php

@@ -429,7 +429,7 @@ function template_main()
 						</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>)';
+							<input type="file" size="60" multiple="multiple" name="attachment[]" id="attachment1" class="input_file" /> (<a href="javascript:void(0);" onclick="cleanFileInput(\'attachment1\');">', $txt['clean_attach'], '</a>)';
 
 			// Show more boxes if they aren't approaching that limit.
 			if ($context['num_allowed_attachments'] > 1)

+ 3 - 3
Themes/default/index.template.php

@@ -218,9 +218,9 @@ function template_body_above()
 				<select name="search_selection">
 					<option value="all"', ($selected == 'all' ? ' selected="selected"' : ''), '>', $txt['search_entireforum'], ' </option>';
 
-		// Can't limit it to a specific topic if we are not in one
-		if (!empty($context['current_topic']))
-			echo '
+			// Can't limit it to a specific topic if we are not in one
+			if (!empty($context['current_topic']))
+				echo '
 					<option value="topic"', ($selected == 'current_topic' ? ' selected="selected"' : ''), '>', $txt['search_thistopic'], '</option>';
 
 		// Can't limit it to a specific board if we are not in one

+ 48 - 8
Themes/default/languages/Admin.english.php

@@ -351,10 +351,8 @@ $txt['attachmentCheckExtensions'] = 'Check attachment\'s extension';
 $txt['attachmentExtensions'] = 'Allowed attachment extensions';
 $txt['attachmentRecodeLineEndings'] = 'Recode line endings in textual attachments';
 $txt['attachmentShowImages'] = 'Display image attachments as pictures under post';
-$txt['attachmentEncryptFilenames'] = 'Encrypt stored filenames';
 $txt['attachmentUploadDir'] = 'Attachments directory';
-$txt['attachmentUploadDir_multiple'] = 'Attachments directory';
-$txt['attachmentUploadDir_multiple_configure'] = 'Configure multiple attachment directories';
+$txt['attachmentUploadDir_multiple_configure'] = 'Manage attachment directories';
 $txt['attachmentDirSizeLimit'] = 'Max attachment folder space';
 $txt['attachmentPostLimit'] = 'Max attachment size per post';
 $txt['attachmentSizeLimit'] = 'Max size per attachment';
@@ -380,17 +378,59 @@ $txt['attach_dir_does_not_exist'] = 'Does Not Exist';
 $txt['attach_dir_not_writable'] = 'Not Writable';
 $txt['attach_dir_files_missing'] = 'Files Missing (<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=repair;%2$s=%1$s">Repair</a>)';
 $txt['attach_dir_unused'] = 'Unused';
+$txt['attach_dir_empty'] = 'Empty';
 $txt['attach_dir_ok'] = 'OK';
-
+$txt['attach_dir_basedir'] = 'Base directory';
+
+$txt['attach_dir_desc'] = 'Create new directories or change the current directory below. Directories can be renamed as long as they do not contain a sub-directory. If the new directory is to be in the created within the forum directory structure. Just the directory name can be used. To remove a directory, blank the path input field. Directories can not be deleted if they contain either files or sub-directories (shown in brackets next to the file count).';
+$txt['attach_dir_base_desc'] = 'You may use below to change the current base directory or create a new one. New base directories are also added to the Attachment Directory list. You may also designate an existing directory to be a base directory.';
+$txt['attach_dir_save_problem'] = 'Oops, there seems to be a problem.';
+$txt['attachments_no_create'] = 'Unable to create a new attachment directory. Please do so using a FTP client or your site file manager.';
+$txt['attachments_no_write'] = 'This directory has been created but is not writable. Please attempt to do so using a FTP client or your site file manager.';
+$txt['attach_dir_duplicate_msg'] = 'Unable to add. This directory already exists.';
+$txt['attach_dir_exists_msg'] = 'Unable to move. A directory already exists at that path.';
+$txt['attach_dir_base_dupe_msg'] = 'Unable to add. This base directory has already been created.';
+$txt['attach_dir_base_no_create'] = 'Unable to create. Please verify the path input. Or create this directory using an FTP client or site file manager and re-try.';
+$txt['attach_dir_no_rename'] = 'Unable to move or rename. Please verify that the path is correct or that this directory does not contain any sub-directories.';
+$txt['attach_dir_no_delete'] = 'Is not empty and can not be deleted. Please do so using a FTP client or site file manager.';
+$txt['attach_dir_no_remove'] = 'Still contains files or is a base directory and can not be deleted.';
+$txt['attach_dir_is_current'] = 'Unable to remove while it is selected as the current directory.';
+$txt['attach_dir_is_current_bd'] = 'Unable to remove while it is selected as the current base directory.';
+$txt['attach_last_dir'] = 'Last active attachment directory';
+$txt['attach_current_dir'] = 'Current attachment directory';
+$txt['attach_current'] = 'Current';
 $txt['attach_path_manage'] = 'Manage Attachment Paths';
-$txt['attach_paths'] = 'Attachment Paths';
-$txt['attach_current_dir'] = 'Current Directory';
+$txt['attach_directories'] = 'Attachment Directories';
+$txt['attach_paths'] = 'Attachment Directory Paths';
+$txt['attach_base_paths'] = 'Base Directory Paths';
 $txt['attach_path'] = 'Path';
-$txt['attach_current_size'] = 'Current Size (KB)';
+$txt['attach_current_size'] = 'Size (KB)';
 $txt['attach_num_files'] = 'Files';
 $txt['attach_dir_status'] = 'Status';
 $txt['attach_add_path'] = 'Add Path';
 $txt['attach_path_current_bad'] = 'Invalid current attachment path.';
+$txt['attachmentDirFileLimit'] = 'Maximum number of files per directory';
+
+$txt['attach_base_paths'] = 'Base Directory Paths';
+$txt['attach_num_dirs'] = 'Directories';
+$txt['max_image_width'] = 'Max display width of posted or attached images';
+$txt['max_image_height'] = 'Max display height of posted or attached images';
+
+$txt['automanage_attachments'] = 'Choose the method for the management of the attachment directories';
+$txt['attachments_normal'] = '(Manual) SMF default behaviour';
+$txt['attachments_auto_years'] = '(Auto) Subdivide by years';
+$txt['attachments_auto_months'] = '(Auto) Subdivide by years and months';
+$txt['attachments_auto_days'] = '(Auto) Subdivide by years, months and days';
+$txt['attachments_auto_16'] = '(Auto) 16 random folders';
+$txt['attachments_auto_16x16'] = '(Auto) 16 random folders with 16 random sub-folders';
+$txt['attachments_auto_space'] = '(Auto) When either directory space limit is reached';
+
+$txt['use_subdirectories_for_attachments'] = 'Create new directories within a base directory';
+$txt['use_subdirectories_for_attachments_note'] = 'Otherwise any new directories will be created within the forum\'s main directory.';
+$txt['basedirectory_for_attachments'] = 'Set a base directory for attachments';
+$txt['basedirectory_for_attachments_current'] = 'Current base directory';
+$txt['basedirectory_for_attachments_warning'] = '<div class="smalltext">Please note that the directory is wrong. <br />(<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">Attempt to correct</a>)</div>';
+$txt['attach_current_dir_warning'] = '<div class="smalltext">There seems to be a problem with this directory. <br />(<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">Attempt to correct</a>)</div>';
 
 $txt['mods_cat_avatars'] = 'Avatars';
 $txt['avatar_directory'] = 'Avatars directory';
@@ -443,7 +483,7 @@ $txt['attach_repair_file_size_of_zero'] = '%1$d attachments/avatars have a size
 $txt['attach_repair_attachment_no_msg'] = '%1$d attachments no longer have a message associated with them';
 $txt['attach_repair_avatar_no_member'] = '%1$d avatars no longer have a member associated with them';
 $txt['attach_repair_wrong_folder'] = '%1$d attachments are in the wrong folder';
-$txt['attach_repair_files_without_attachment'] = '%1$d files doesn\'t have a corresponding entri into the database. (These will be deleted)';
+$txt['attach_repair_files_without_attachment'] = '%1$d files do not have a corresponding entry in the database. (These will be deleted)';
 
 $txt['news_title'] = 'News and Newsletters';
 $txt['news_settings_desc'] = 'Here you can change the settings and permissions related to news and newsletters.';

+ 1 - 1
Themes/default/languages/Help.english.php

@@ -290,7 +290,7 @@ $helptxt['timeLoadPageEnable'] = 'This will show the time in seconds SMF took to
 $helptxt['removeNestedQuotes'] = 'This will strip nested quotes from a post when citing the post in question via a quote link.';
 $helptxt['simpleSearch'] = 'This will show a simple search form and a link to a more advanced form.';
 $helptxt['search_dropdown'] = 'This will show a search selection dropdown next to the quick search box.  From this you can choose to search the current site, current board (if in a board_, current topic (if in a topic) or search for members.';
-$helptxt['max_image_width'] = 'This allows you to set a maximum size for posted pictures. Pictures smaller than the maximum will not be affected.';
+$helptxt['max_image_width'] = 'This allows you to set a maximum size for posted pictures. Pictures smaller than the maximum will not be affected. This also determines how attached images are displayed when a thumbnail is clicked on.';
 $helptxt['mail_type'] = 'This setting allows you to choose either PHP\'s default settings, or to override those settings with SMTP.  PHP doesn\'t support using authentication with SMTP (which many hosts require, now) so if you want that you should select SMTP.  Please note that SMTP can be slower, and some servers will not take usernames and passwords.<br /><br />You don\'t need to fill in the SMTP settings if this is set to PHP\'s default.';
 $helptxt['attachment_manager_settings'] = 'Attachments are files that members can upload, and attach to a post.<br /><br />
 		<strong>Check attachment extension</strong>:<br /> Do you want to check the extension of the files?<br />

+ 0 - 2
Themes/default/languages/ManageSettings.english.php

@@ -71,8 +71,6 @@ $txt['jquery_cdn'] = 'Google CDN';
 $txt['jquery_auto'] = 'Auto';
 $txt['queryless_urls'] = 'Search engine friendly URLs';
 $txt['queryless_urls_note'] = 'Apache/Lighttpd only!';
-$txt['max_image_width'] = 'Max width of posted pictures (0 = disable)';
-$txt['max_image_height'] = 'Max height of posted pictures (0 = disable)';
 $txt['enableReportPM'] = 'Enable reporting of personal messages';
 $txt['max_pm_recipients'] = 'Maximum number of recipients allowed in a personal message';
 $txt['max_pm_recipients_note'] = '(0 for no limit, admins are exempt)';

+ 3 - 2
Themes/default/languages/Post.english.php

@@ -198,7 +198,6 @@ $txt['digest_mod_act_split'] = '"%1$s" was split';
 $txt['attach_error_title'] = 'Error uploading 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 area of your admin panel.';
@@ -228,8 +227,10 @@ $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['ran_out_of_space'] = 'The upload folder is full. Please contact an administrator about this problem.';
 $txt['attachments_no_write'] = 'The attachments upload directory is not writable.  Your attachment or avatar cannot be saved.';
+$txt['attachments_no_create'] = 'Unable to create a new attachment directory.  Your attachment or avatar cannot be saved.';
 $txt['attachments_limit_per_post'] = 'You may not upload more than %1$d attachments per post';
 
+
 ?>

+ 1 - 0
Themes/default/languages/index.english.php

@@ -165,6 +165,7 @@ $txt['search_thistopic'] = 'This topic';
 $txt['search_members'] = 'Members';
 
 $txt['back'] = 'Back';
+$txt['continue'] = 'Continue';
 $txt['password_reminder'] = 'Password reminder';
 $txt['topic_started'] = 'Topic started by';
 $txt['title'] = 'Title';

+ 55 - 0
Themes/default/scripts/admin.js

@@ -622,4 +622,59 @@ function select_in_category(cat_id, elem, brd_list)
 		document.getElementById(elem.value + '_brd' + brd_list[brd]).checked = true;
 
 	elem.selectedIndex = 0;
+}
+
+/*
+* Server Settings > Caching
+*/
+function toggleCache ()
+{
+	var memcache = document.getElementById('cache_memcached');
+	var cachedir = document.getElementById('cachedir');
+	memcache.disabled = cache_type.value != "memcached";
+	cachedir.disabled = cache_type.value != "smf";
+}
+
+/*
+* Attachments Settings
+*/
+function toggleSubDir ()
+{
+	var auto_attach = document.getElementById('automanage_attachments');
+	var use_sub_dir = document.getElementById('use_subdirectories_for_attachments');
+	var dir_elem = document.getElementById('basedirectory_for_attachments');
+
+	use_sub_dir.disabled = !Boolean(auto_attach.selectedIndex);
+	if (use_sub_dir.disabled)
+	{
+		use_sub_dir.style.display = "none";
+		document.getElementById('setting_use_subdirectories_for_attachments').parentNode.style.display = "none";
+		dir_elem.style.display = "none";
+		document.getElementById('setting_basedirectory_for_attachments').parentNode.style.display = "none";
+		document.getElementById('attachmentUploadDir').parentNode.style.display = "";
+		document.getElementById('setting_attachmentUploadDir').parentNode.style.display = "";
+	}
+	else
+	{
+		use_sub_dir.style.display = "";
+		document.getElementById('setting_use_subdirectories_for_attachments').parentNode.style.display = "";
+		dir_elem.style.display = "";
+		document.getElementById('setting_basedirectory_for_attachments').parentNode.style.display = "";
+		document.getElementById('attachmentUploadDir').parentNode.style.display = "none";
+		document.getElementById('setting_attachmentUploadDir').parentNode.style.display = "none";
+	}
+		toggleBaseDir();
+}
+function toggleBaseDir ()
+{
+	var auto_attach = document.getElementById('automanage_attachments');
+	var sub_dir = document.getElementById('use_subdirectories_for_attachments');
+	var dir_elem = document.getElementById('basedirectory_for_attachments');
+
+	if (auto_attach.selectedIndex == 0)
+	{
+		dir_elem.disabled = 1;
+	}
+	else
+		dir_elem.disabled = !sub_dir.checked;
 }

+ 1 - 0
other/install_2-1_mysql.sql

@@ -1691,6 +1691,7 @@ VALUES ('smfVersion', '{$smf_version}'),
 	('attachmentThumbnails', '1'),
 	('attachmentThumbWidth', '150'),
 	('attachmentThumbHeight', '150'),
+	('use_subdirectories_for_attachments', '1'),
 	('censorIgnoreCase', '1'),
 	('mostOnline', '1'),
 	('mostOnlineToday', '1'),

+ 1 - 0
other/install_2-1_postgresql.sql

@@ -2180,6 +2180,7 @@ INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentEncryptFil
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentThumbnails', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentThumbWidth', '150');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentThumbHeight', '150');
+INSERT INTO {$db_prefix}settings (variable, value) VALUES ('use_subdirectories_for_attachments', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('censorIgnoreCase', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('mostOnline', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('mostOnlineToday', '1');

+ 1 - 0
other/install_2-1_sqlite.sql

@@ -1833,6 +1833,7 @@ INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentEncryptFil
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentThumbnails', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentThumbWidth', '150');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachmentThumbHeight', '150');
+INSERT INTO {$db_prefix}settings (variable, value) VALUES ('use_subdirectories_for_attachments', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('censorIgnoreCase', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('mostOnline', '1');
 INSERT INTO {$db_prefix}settings (variable, value) VALUES ('mostOnlineToday', '1');