Browse Source

Merge branch 'master' of https://github.com/SimpleMachines/SMF2.1

emanuele 12 years ago
parent
commit
a13a9645b4

+ 2 - 1
Sources/Admin.php

@@ -30,7 +30,7 @@ function AdminMain()
 	// Load the language and templates....
 	loadLanguage('Admin');
 	loadTemplate('Admin', 'admin');
-	loadJavascriptFile('admin.js?alp21', array('default_theme' => true), 'admin.js');
+	loadJavascriptFile('admin.js', array('default_theme' => true), 'admin.js');
 
 	// No indexing evil stuff.
 	$context['robot_no_index'] = true;
@@ -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;
+}
+
+?>

+ 5 - 5
Sources/Drafts.php

@@ -384,11 +384,11 @@ function DeleteDraft($id_draft, $check = true)
 
 	$smcFunc['db_query']('', '
 		DELETE FROM {db_prefix}user_drafts
-		WHERE draft_id IN ({array_int:draft_id})', ($check ? '
-			AND  id_member = {int:id_member}' : ''), '
+		WHERE id_draft IN ({array_int:id_draft})' . ($check ? '
+			AND  id_member = {int:id_member}' : '') . '
 		LIMIT 1',
 		array (
-			'draft_id' => $id_draft,
+			'id_draft' => $id_draft,
 			'id_member' => empty($user_info['id']) ? -1 : $user_info['id'],
 		)
 	);
@@ -667,8 +667,8 @@ function showPMDrafts($memID = -1)
 	if (!empty($_REQUEST['id_draft']) && !empty($context['drafts_pm_save']) && $memID == $user_info['id'])
 	{
 		checkSession('get');
-		$draft_id = (int) $_REQUEST['id_draft'];
-		redirectexit('action=pm;sa=send;id_draft=' . $draft_id);
+		$id_draft = (int) $_REQUEST['id_draft'];
+		redirectexit('action=pm;sa=send;id_draft=' . $id_draft);
 	}
 
 	// Default to 10.

+ 61 - 41
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'))
 	{
@@ -1703,17 +1714,17 @@ function loadTheme($id_theme = 0, $initialize = true)
 	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
 		loadJavascriptFile('https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', array(), 'jquery');
 	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
-		loadJavascriptFile('jquery-1.7.1.min.js', array('default_theme' => true), 'jquery');
+		loadJavascriptFile('jquery-1.7.1.min.js', array('default_theme' => true, 'seed' => false), 'jquery');
 	// Auto loading? template_javascript() will take care of the local half of this.
 	else
 		loadJavascriptFile('https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', array(), 'jquery');
 
 	// Queue our JQuery plugins!
-	loadJavascriptFile('smf_jquery_plugins.js?alp21', array('default_theme' => true), 'jquery_plugins');
+	loadJavascriptFile('smf_jquery_plugins.js', array('default_theme' => true));
 
 	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
-	loadJavascriptFile('script.js?alp21', array('default_theme' => true), 'smf_scripts');
-	loadJavascriptFile('theme.js?alp21', array(), 'theme_scripts');
+	loadJavascriptFile('script.js', array('default_theme' => true), 'smf_scripts');
+	loadJavascriptFile('theme.js', array(), 'theme_scripts');
 
 	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
 	if ((!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time() && empty($modSettings['mail_queue_use_cron'])) || empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
@@ -1884,88 +1895,97 @@ function loadSubTemplate($sub_template_name, $fatal = false)
 /**
  * Add a CSS file for output later
  *
- * Options are the following:
- * 	- local (true/false): define if the file is local
- * 	- default_theme (true/false): force use of default theme url
- * 	- force_current (true/false): if this is false, we will attempt to load the file from the default theme if not found in the current theme
- *  - validate (true/false): if true script will validate the local file exists
- *
  * @param string $filename
- * @param array $options
+ * @param array $params
+ * Keys are the following:
+ * 	- ['local'] (true/false): define if the file is local
+ * 	- ['default_theme'] (true/false): force use of default theme url
+ * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the default theme if not found in the current theme
+ *  - ['validate'] (true/false): if true script will validate the local file exists
+ *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
  * @param string $id
  */
-function loadCSSFile($filename, $options = array(), $id = '')
+function loadCSSFile($filename, $params = array(), $id = '')
 {
 	global $settings, $context;
 
-	$options['force_current'] = !empty($options['force_current']) ? $options['force_current'] : false;
-	$theme = !empty($options['default_theme']) ? 'default_theme' : 'theme';
-	$id = empty($id) ? basename($filename, '.css?alp21') : $id;
+	$params['seed'] = (!isset($params['seed']) || $params['seed'] === true) ? '?alph21' : (is_string($params['seed']) ? ($params['seed'] = $params['seed'][0] === '?' ? $params['seed'] : '?' . $params['seed']) : '');
+	$params['force_current'] = !empty($params['force_current']) ? $params['force_current'] : false;
+	$theme = !empty($params['default_theme']) ? 'default_theme' : 'theme';
+	
+	// account for shorthand like admin.css?alp21 filenames
+	$has_seed = strpos($filename, '.css?');
+	$id = empty($id) ? strtr(basename($filename), '?', '_') : $id;
 
 	// Is this a local file?
-	if (strpos($filename, 'http') === false || !empty($options['local']))
+	if (strpos($filename, 'http') === false || !empty($params['local']))
 	{
 		// Are we validating the the file exists?
-		if (!empty($options['validate']) && !file_exists($settings[$theme . '_dir'] . '/css/' . $filename))
+		if (!empty($params['validate']) && !file_exists($settings[$theme . '_dir'] . '/css/' . $filename))
 		{
 			// Maybe the default theme has it?
-			if ($theme === 'theme' && !$options['force_current'] && file_exists($settings['default_theme_dir'] . '/' . $filename))
-				$filename = $settings['default_theme_url'] . '/css/' . $filename;
+			if ($theme === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/' . $filename))
+				$filename = $settings['default_theme_url'] . '/css/' . $filename . ($has_seed ? '' : $params['seed']);
 			else
 				$filename = false;
 		}
 		else
-			$filename = $settings[$theme . '_url'] . '/css/' . $filename;
+			$filename = $settings[$theme . '_url'] . '/css/' . $filename . ($has_seed ? '' : $params['seed']);
 	}
 
 	// Add it to the array for use in the template
 	if (!empty($filename))
-		$context['css_files'][$id] = array('filename' => $filename, 'options' => $options);
+		$context['css_files'][$id] = array('filename' => $filename, 'options' => $params);
 }
 
 /**
  * Add a Javascript file for output later
- *
- * Options are the following:
- * 	- local (true/false): define if the file is local
- * 	- default_theme (true/false): force use of default theme url
- * 	- defer (true/false): define if the file should load in <head> or before the closing <html> tag
- * 	- force_current (true/false): if this is false, we will attempt to load the file from the
+ 
+ * @param string $filename
+ * @param array $params
+ * Keys are the following:
+ * 	- ['local'] (true/false): define if the file is local
+ * 	- ['default_theme'] (true/false): force use of default theme url
+ * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
+ * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
  *    default theme if not found in the current theme
- *	- async (true/false): if the script should be loaded asynchronously (HTML5)
- *  - validate (true/false): if true script will validate the local file exists
+ *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
+ *  - ['validate'] (true/false): if true script will validate the local file exists
+ *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
  *
- * @param string $filename
- * @param array $options
  * @param string $id
  */
-function loadJavascriptFile($filename, $options = array(), $id = '')
+function loadJavascriptFile($filename, $params = array(), $id = '')
 {
 	global $settings, $context;
 
-	$options['force_current'] = !empty($options['force_current']) ? $options['force_current'] : false;
-	$theme = !empty($options['default_theme']) ? 'default_theme' : 'theme';
-	$id = empty($id) ? basename($filename, '.js?alp21') : $id;
+	$params['seed'] = (!isset($params['seed']) || $params['seed'] === true) ? '?alph21' : (is_string($params['seed']) ? ($params['seed'] = $params['seed'][0] === '?' ? $params['seed'] : '?' . $params['seed']) : '');
+	$params['force_current'] = !empty($params['force_current']) ? $params['force_current'] : false;
+	$theme = !empty($params['default_theme']) ? 'default_theme' : 'theme';
+	
+	// account for shorthand like admin.js?alp21 filenames
+	$has_seed = strpos($filename, '.js?');
+	$id = empty($id) ? strtr(basename($filename), '?', '_') : $id;
 
 	// Is this a local file?
-	if (strpos($filename, 'http') === false || !empty($options['local']))
+	if (strpos($filename, 'http') === false || !empty($params['local']))
 	{
 		// Are we validating it exists on disk?
-		if (!empty($options['validate']) && !file_exists($settings[$theme . '_dir'] . '/scripts/' . $filename))
+		if (!empty($params['validate']) && !file_exists($settings[$theme . '_dir'] . '/scripts/' . $filename))
 		{
 			// can't find it in this theme, how about the default?
-			if ($theme === 'theme' && !$options['force_current'] && file_exists($settings['default_theme_dir'] . '/' . $filename))
-				$filename = $settings['default_theme_url'] . '/scripts/' . $filename;
+			if ($theme === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/' . $filename))
+				$filename = $settings['default_theme_url'] . '/scripts/' . $filename . ($has_seed ? '' : $params['seed']);
 			else
 				$filename = false;
 		}
 		else
-			$filename = $settings[$theme . '_url'] . '/scripts/' . $filename;
+			$filename = $settings[$theme . '_url'] . '/scripts/' . $filename . ($has_seed ? '' : $params['seed']);
 	}
 
 	// Add it to the array for use in the template
 	if (!empty($filename))
-		$context['javascript_files'][$id] = array('filename' => $filename, 'options' => $options);
+		$context['javascript_files'][$id] = array('filename' => $filename, 'options' => $params);
 }
 
 /**

+ 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);
 }
 
 ?>

+ 2 - 2
Sources/ManageMaintenance.php

@@ -1904,10 +1904,10 @@ function MaintainRemoveOldDrafts()
 	);
 
 	while ($row = $smcFunc['db_fetch_row']($request))
-		$drafts[] = $row[0];
+		$drafts[] = (int) $row[0];
 	$smcFunc['db_free_result']($request);
 
-	// If we have old one, remove them
+	// If we have old drafts, remove them
 	if (count($drafts) > 0)
 	{
 		require_once($sourcedir . '/Drafts.php');

+ 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'),

+ 0 - 9
Sources/MessageIndex.php

@@ -636,15 +636,6 @@ function MessageIndex()
 				'use_permissions' => true,
 				'selected_board' => empty($_SESSION['move_to_topic']) ? null : $_SESSION['move_to_topic'],
 			);
-			$context['move_to_boards'] = getBoardList($boardListOptions);
-
-			// Make the boards safe for display.
-			foreach ($context['move_to_boards'] as $id_cat => $cat)
-			{
-				$context['move_to_boards'][$id_cat]['name'] = strip_tags($cat['name']);
-				foreach ($cat['boards'] as $id_board => $aboard)
-					$context['move_to_boards'][$id_cat]['boards'][$id_board]['name'] = strip_tags($aboard['name']);
-			}
 
 			// With no other boards to see, it's useless to move.
 			if (empty($context['move_to_boards']))

+ 1 - 1
Sources/ModerationCenter.php

@@ -213,7 +213,7 @@ function ModerationHome()
 	global $txt, $context, $scripturl, $modSettings, $user_info, $user_settings;
 
 	loadTemplate('ModerationCenter');
-	loadJavascriptFile('admin.js?alp21', array('default_theme' => true));
+	loadJavascriptFile('admin.js', array('default_theme' => true), 'admin.js');
 
 	$context['page_title'] = $txt['moderation_center'];
 	$context['sub_template'] = 'moderation_center';

+ 2 - 2
Sources/Packages.php

@@ -645,7 +645,7 @@ function PackageInstallTest()
 		if (empty($thisAction))
 			continue;
 
-		if (!file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']))
+		if (isset($action['filename']) && !!file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']))
 		{
 			$context['has_failure'] = true;
 
@@ -1383,7 +1383,7 @@ function PackageBrowse()
 				'id' . $type => array(
 					'header' => array(
 						'value' => $txt['package_id'],
-						'style' => 'width: 32px;',
+						'style' => 'width: 40px;',
 					),
 					'data' => array(
 						'function' => create_function('$package_md5', '

+ 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',

+ 1 - 1
Sources/ScheduledTasks.php

@@ -1747,7 +1747,7 @@ function scheduled_remove_old_drafts()
 	);
 
 	while ($row = $smcFunc['db_fetch_row']($request))
-		$drafts[] = $row[0];
+		$drafts[] = (int) $row[0];
 	$smcFunc['db_free_result']($request);
 
 	// If we have old one, remove them

+ 0 - 12
Sources/Search.php

@@ -2056,18 +2056,6 @@ function prepareSearchContext($reset = false)
 		$context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']);
 		$context['can_markread'] = $context['user']['is_logged'];
 
-		// If we've found a message we can move, and we don't already have it, load the destinations.
-		if ($options['display_quick_mod'] == 1 && !isset($context['move_to_boards']) && $context['can_move'])
-		{
-			require_once($sourcedir . '/Subs-MessageIndex.php');
-			$boardListOptions = array(
-				'use_permissions' => true,
-				'not_redirection' => true,
-				'selected_board' => empty($_SESSION['move_to_topic']) ? null : $_SESSION['move_to_topic'],
-			);
-			$context['move_to_boards'] = getBoardList($boardListOptions);
-		}
-
 		$context['qmod_actions'] = array('remove', 'lock', 'sticky', 'move', 'merge', 'restore', 'markread');
 		call_integration_hook('integrate_quick_mod_actions_search');
 	}

+ 6 - 7
Sources/Subs-Editor.php

@@ -1469,13 +1469,12 @@ function create_control_richedit($editorOptions)
 		$settings['smileys_url'] = $modSettings['smileys_url'] . '/' . $user_info['smiley_set'];
 
 		// This really has some WYSIWYG stuff.
-		loadTemplate('GenericControls', isBrowser('ie') ? 'editor_ie' : 'editor');
+		loadTemplate('GenericControls', 'jquery.sceditor');
 
-		// Css and JS make the editor go round
-		loadCSSFile( $settings['default_theme_url'] . '/css/jquery.sceditor.css', array());
-		loadJavascriptFile($settings['default_theme_url'] . '/scripts/editor.js?alp21', array(), 'editor.js');
-		loadJavascriptFile($settings['default_theme_url'] . '/scripts/jquery.sceditor.js?alp21', array());
-		loadJavascriptFile($settings['default_theme_url'] . '/scripts/jquery.sceditor.bbcode.js?alp21', array());
+		// JS makes the editor go round
+		loadJavascriptFile('editor.js', array('default_theme' => true), 'smf_editor');
+		loadJavascriptFile('jquery.sceditor.js', array('default_theme' => true));
+		loadJavascriptFile('jquery.sceditor.bbcode.js', array('default_theme' => true));
 		addInlineJavascript('
 		var smf_smileys_url = \'' . $settings['smileys_url'] . '\';
 		var bbc_quote_from = \'' . addcslashes($txt['quote_from'], "'") . '\';
@@ -1488,7 +1487,7 @@ function create_control_richedit($editorOptions)
 		$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
 		if ($context['show_spellchecking'])
 		{
-			loadJavascriptFile($settings['default_theme_url'] . '/scripts/spellcheck.js?alp21', array());
+			loadJavascriptFile('spellcheck.js', array('default_theme' => true));
 
 			// Some hidden information is needed in order to make the spell checking work.
 			if (!isset($_REQUEST['xml']))

+ 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');
 }
 

+ 25 - 19
Themes/default/MessageIndex.template.php

@@ -317,7 +317,7 @@ function template_main()
 			echo '
 				<tr class="titlebg">
 					<td colspan="6" align="right">
-						<select class="qaction" name="qaction"', $context['can_move'] ? ' onchange="this.form.moveItTo.disabled = (this.options[this.selectedIndex].value != \'move\');"' : '', '>
+						<select class="qaction" name="qaction"', $context['can_move'] ? ' onchange="this.form.move_to.disabled = (this.options[this.selectedIndex].value != \'move\');"' : '', '>
 							<option value="">--------</option>';
 
 			foreach ($context['qmod_actions'] as $qmod_action)
@@ -330,23 +330,8 @@ function template_main()
 
 			// Show a list of boards they can move the topic to.
 			if ($context['can_move'])
-			{
-					echo '
-						<select class="qaction" id="moveItTo" name="move_to" disabled="disabled">';
-
-					foreach ($context['move_to_boards'] as $category)
-					{
-						echo '
-							<optgroup label="', $category['name'], '">';
-						foreach ($category['boards'] as $board)
-								echo '
-								<option value="', $board['id'], '"', $board['selected'] ? ' selected="selected"' : '', '>', $board['child_level'] > 0 ? str_repeat('==', $board['child_level'] - 1) . '=&gt;' : '', ' ', $board['name'], '</option>';
-						echo '
-							</optgroup>';
-					}
-					echo '
-						</select>';
-			}
+				echo '
+			<span id="quick_mod_jump_to">&nbsp;</span>';
 
 			echo '
 						<input type="submit" value="', $txt['quick_mod_go'], '" onclick="return document.forms.quickModForm.qaction.value != \'\' &amp;&amp; confirm(\'', $txt['quickmod_confirm'], '\');" class="button_submit qaction" />
@@ -396,7 +381,28 @@ function template_main()
 			</p>';
 
 	echo '
-			<script type="text/javascript"><!-- // --><![CDATA[
+			<script type="text/javascript"><!-- // --><![CDATA[';
+
+	if (!empty($context['can_quick_mod']) && $options['display_quick_mod'] == 1 && !empty($context['topics']) && $context['can_move'])
+		echo '
+				if (typeof(window.XMLHttpRequest) != "undefined")
+					aJumpTo[aJumpTo.length] = new JumpTo({
+						sContainerId: "quick_mod_jump_to",
+						sClassName: "qaction",
+						sJumpToTemplate: "%dropdown_list%",
+						iCurBoardId: ', $context['current_board'], ',
+						iCurBoardChildLevel: ', $context['jump_to']['child_level'], ',
+						sCurBoardName: "', $context['jump_to']['board_name'], '",
+						sBoardChildLevelIndicator: "==",
+						sBoardPrefix: "=> ",
+						sCatSeparator: "-----------------------------",
+						sCatPrefix: "",
+						bNoRedirect: true,
+						bDisabled: true,
+						sCustomName: "move_to"
+					});';
+
+	echo '
 				if (typeof(window.XMLHttpRequest) != "undefined")
 					aJumpTo[aJumpTo.length] = new JumpTo({
 						sContainerId: "message_index_jump_to",

+ 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)

+ 23 - 19
Themes/default/Search.template.php

@@ -395,7 +395,7 @@ function template_results()
 			echo '
 			<div class="titlebg2" style="padding: 4px;">
 				<div class="floatright">
-					<select name="qaction"', $context['can_move'] ? ' onchange="this.form.moveItTo.disabled = (this.options[this.selectedIndex].value != \'move\');"' : '', '>
+					<select class="qaction" name="qaction"', $context['can_move'] ? ' onchange="this.form.move_to.disabled = (this.options[this.selectedIndex].value != \'move\');"' : '', '>
 						<option value="">--------</option>';
 
 			foreach ($context['qmod_actions'] as $qmod_action)
@@ -407,23 +407,8 @@ function template_results()
 					</select>';
 
 			if ($context['can_move'])
-			{
-					echo '
-					<select id="moveItTo" name="move_to" disabled="disabled">';
-
-					foreach ($context['move_to_boards'] as $category)
-					{
-						echo '
-						<optgroup label="', $category['name'], '">';
-						foreach ($category['boards'] as $board)
-								echo '
-						<option value="', $board['id'], '"', $board['selected'] ? ' selected="selected"' : '', '>', $board['child_level'] > 0 ? str_repeat('==', $board['child_level'] - 1) . '=&gt;' : '', ' ', $board['name'], '</option>';
-						echo '
-						</optgroup>';
-					}
-					echo '
-					</select>';
-			}
+				echo '
+				<span id="quick_mod_jump_to">&nbsp;</span>';
 
 			echo '
 					<input type="hidden" name="redirect_url" value="', $scripturl . '?action=search2;params=' . $context['params'], '" />
@@ -513,7 +498,26 @@ function template_results()
 	echo '
 		<br class="clear" />
 		<div class="smalltext righttext" id="search_jump_to">&nbsp;</div>
-		<script type="text/javascript"><!-- // --><![CDATA[
+		<script type="text/javascript"><!-- // --><![CDATA[';
+
+	if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && !empty($context['topics']) && $context['can_move'])
+		echo '
+				if (typeof(window.XMLHttpRequest) != "undefined")
+					aJumpTo[aJumpTo.length] = new JumpTo({
+						sContainerId: "quick_mod_jump_to",
+						sClassName: "qaction",
+						sJumpToTemplate: "%dropdown_list%",
+						sCurBoardName: "', $context['jump_to']['board_name'], '",
+						sBoardChildLevelIndicator: "==",
+						sBoardPrefix: "=> ",
+						sCatSeparator: "-----------------------------",
+						sCatPrefix: "",
+						bNoRedirect: true,
+						bDisabled: true,
+						sCustomName: "move_to"
+					});';
+
+	echo '
 			if (typeof(window.XMLHttpRequest) != "undefined")
 				aJumpTo[aJumpTo.length] = new JumpTo({
 					sContainerId: "search_jump_to",

+ 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;
 }

+ 30 - 15
Themes/default/scripts/script.js

@@ -1016,10 +1016,11 @@ function createEventListener(oTarget)
 }
 
 // This function will retrieve the contents needed for the jump to boxes.
-function grabJumpToContent()
+function grabJumpToContent(elem)
 {
 	var oXMLDoc = getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=jumpto;xml');
 	var aBoardsAndCategories = new Array();
+	var bIE5x = !('implementation' in document);
 
 	ajax_indicator(true);
 
@@ -1042,6 +1043,14 @@ function grabJumpToContent()
 
 	for (var i = 0, n = aJumpTo.length; i < n; i++)
 		aJumpTo[i].fillSelect(aBoardsAndCategories);
+
+	if (bIE5x)
+		elem.options[iIndexPointer].selected = true;
+
+	// Internet Explorer needs this to keep the box dropped down.
+	elem.style.width = 'auto';
+	elem.focus();
+
 }
 
 // This'll contain all JumpTo objects on the page.
@@ -1061,7 +1070,7 @@ JumpTo.prototype.showSelect = function ()
 	var sChildLevelPrefix = '';
 	for (var i = this.opt.iCurBoardChildLevel; i > 0; i--)
 		sChildLevelPrefix += this.opt.sBoardChildLevelIndicator;
-	setInnerHTML(document.getElementById(this.opt.sContainerId), this.opt.sJumpToTemplate.replace(/%select_id%/, this.opt.sContainerId + '_select').replace(/%dropdown_list%/, '<select name="' + this.opt.sContainerId + '_select" id="' + this.opt.sContainerId + '_select" ' + ('implementation' in document ? '' : 'onmouseover="grabJumpToContent();" ') + ('onbeforeactivate' in document ? 'onbeforeactivate' : 'onfocus') + '="grabJumpToContent();"><option value="?board=' + this.opt.iCurBoardId + '.0">' + sChildLevelPrefix + this.opt.sBoardPrefix + this.opt.sCurBoardName.removeEntities() + '</option></select>&nbsp;<input type="button" class="button_submit" value="' + this.opt.sGoButtonLabel + '" onclick="window.location.href = \'' + smf_prepareScriptUrl(smf_scripturl) + 'board=' + this.opt.iCurBoardId + '.0\';" />'));
+	setInnerHTML(document.getElementById(this.opt.sContainerId), this.opt.sJumpToTemplate.replace(/%select_id%/, this.opt.sContainerId + '_select').replace(/%dropdown_list%/, '<select ' + (this.opt.bDisabled == true ? 'disabled="disabled" ' : 0) + (this.opt.sClassName != undefined ? 'class="' + this.opt.sClassName + '" ' : '') + 'name="' + (this.opt.sCustomName != undefined ? this.opt.sCustomName : this.opt.sContainerId + '_select') + '" id="' + this.opt.sContainerId + '_select" ' + ('implementation' in document ? '' : 'onmouseover="grabJumpToContent(this);" ') + ('onbeforeactivate' in document ? 'onbeforeactivate' : 'onfocus') + '="grabJumpToContent(this);"><option value="' + (this.opt.bNoRedirect != undefined && this.opt.bNoRedirect == true ? this.opt.iCurBoardId : '?board=' + this.opt.iCurBoardId + '.0') + '">' + sChildLevelPrefix + this.opt.sBoardPrefix + this.opt.sCurBoardName.removeEntities() + '</option></select>&nbsp;' + (this.opt.sGoButtonLabel != undefined ? '<input type="button" class="button_submit" value="' + this.opt.sGoButtonLabel + '" onclick="window.location.href = \'' + smf_prepareScriptUrl(smf_scripturl) + 'board=' + this.opt.iCurBoardId + '.0\';" />' : '')));
 	this.dropdownList = document.getElementById(this.opt.sContainerId + '_select');
 }
 
@@ -1081,6 +1090,9 @@ JumpTo.prototype.fillSelect = function (aBoardsAndCategories)
 	else
 		this.dropdownList.onfocus = null;
 
+	if (this.opt.bNoRedirect)
+		this.dropdownList.options[0].disabled = 'disabled';
+
 	// Create a document fragment that'll allowing inserting big parts at once.
 	var oListFragment = document.createDocumentFragment();
 
@@ -1105,7 +1117,15 @@ JumpTo.prototype.fillSelect = function (aBoardsAndCategories)
 
 		oOption = document.createElement('option');
 		oOption.appendChild(document.createTextNode((aBoardsAndCategories[i].isCategory ? this.opt.sCatPrefix : sChildLevelPrefix + this.opt.sBoardPrefix) + aBoardsAndCategories[i].name));
-		oOption.value = aBoardsAndCategories[i].isCategory ? '#c' + aBoardsAndCategories[i].id : '?board=' + aBoardsAndCategories[i].id + '.0';
+		if (!this.opt.bNoRedirect)
+			oOption.value = aBoardsAndCategories[i].isCategory ? '#c' + aBoardsAndCategories[i].id : '?board=' + aBoardsAndCategories[i].id + '.0';
+		else
+		{
+			if (aBoardsAndCategories[i].isCategory)
+				oOption.disabled = 'disabled';
+			else
+				oOption.value = aBoardsAndCategories[i].id;
+		}
 		oListFragment.appendChild(oOption);
 
 		if (aBoardsAndCategories[i].isCategory)
@@ -1115,15 +1135,12 @@ JumpTo.prototype.fillSelect = function (aBoardsAndCategories)
 	// Add the remaining items after the currently selected item.
 	this.dropdownList.appendChild(oListFragment);
 
-	// Internet Explorer needs this to keep the box dropped down.
-	this.dropdownList.style.width = 'auto';
-	this.dropdownList.focus();
-
 	// Add an onchange action
-	this.dropdownList.onchange = function() {
-		if (this.selectedIndex > 0 && this.options[this.selectedIndex].value)
-			window.location.href = smf_scripturl + this.options[this.selectedIndex].value.substr(smf_scripturl.indexOf('?') == -1 || this.options[this.selectedIndex].value.substr(0, 1) != '?' ? 0 : 1);
-	}
+	if (!this.opt.bNoRedirect)
+		this.dropdownList.onchange = function() {
+			if (this.selectedIndex > 0 && this.options[this.selectedIndex].value)
+				window.location.href = smf_scripturl + this.options[this.selectedIndex].value.substr(smf_scripturl.indexOf('?') == -1 || this.options[this.selectedIndex].value.substr(0, 1) != '?' ? 0 : 1);
+		}
 }
 
 // A global array containing all IconList objects.
@@ -1188,9 +1205,7 @@ IconList.prototype.openPopup = function (oDiv, iMessageId)
 
 		// Start to fetch its contents.
 		ajax_indicator(true);
-		this.tmpMethod = getXMLDocument;
-		this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=xmlhttp;sa=messageicons;board=' + this.opt.iBoardId + ';xml', this.onIconsReceived);
-		delete this.tmpMethod;
+		sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=messageicons;board=' + this.opt.iBoardId + ';xml', '', this.onIconsReceived);
 
 		createEventListener(document.body);
 	}
@@ -1247,7 +1262,7 @@ IconList.prototype.onItemMouseDown = function (oDiv, sNewIcon)
 	{
 		ajax_indicator(true);
 		this.tmpMethod = getXMLDocument;
-		var oXMLDoc = this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=jsmodify;topic=' + this.opt.iTopicId + ';msg=' + this.iCurMessageId + ';' + smf_session_var + '=' + smf_session_id + ';icon=' + sNewIcon + ';xml');
+		var oXMLDoc = this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=jsmodify;topic=' + this.opt.iTopicId + ';msg=' + this.iCurMessageId + ';' + smf_session_var + '=' + smf_session_id + ';icon=' + sNewIcon + ';xml');
 		delete this.tmpMethod;
 		ajax_indicator(false);
 

+ 2 - 4
Themes/default/scripts/stats.js

@@ -180,10 +180,8 @@ smf_StatsCenter.prototype.onBeforeExpandMonth = function (oToggle)
 
 	if (oToggle.opt.aSwappableContainers.length == 0)
 	{
-		// A complicated way to call getXMLDocument, but stay in scope.
-		this.tmpMethod = getXMLDocument;
-		this.oXmlRequestHandle = this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=stats;expand=' + oToggle.opt.sMonthId + ';xml', this.onDocReceived);
-		delete this.tmpMethod;
+		// Make the xml call
+		sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + 'action=stats;expand=' + oToggle.opt.sMonthId + ';xml', '', this.onDocReceived);
 
 		if ('ajax_indicator' in window)
 			ajax_indicator(true);

+ 1 - 3
Themes/default/scripts/suggest.js

@@ -622,9 +622,7 @@ smc_AutoSuggest.prototype.autoSuggestUpdate = function ()
 	sSearchString = sSearchString.php_to8bit().php_urlencode();
 
 	// Get the document.
-	this.tmpMethod = getXMLDocument;
-	this.oXmlRequestHandle = this.tmpMethod(this.sRetrieveURL.replace(/%scripturl%/g, smf_prepareScriptUrl(smf_scripturl)).replace(/%suggest_type%/g, this.opt.sSearchType).replace(/%search%/g, sSearchString).replace(/%sessionVar%/g, this.opt.sSessionVar).replace(/%sessionID%/g, this.opt.sSessionId).replace(/%time%/g, new Date().getTime()), this.onSuggestionReceived);
-	delete this.tmpMethod;
+	sendXMLDocument.call(this, this.sRetrieveURL.replace(/%scripturl%/g, smf_prepareScriptUrl(smf_scripturl)).replace(/%suggest_type%/g, this.opt.sSearchType).replace(/%search%/g, sSearchString).replace(/%sessionVar%/g, this.opt.sSessionVar).replace(/%sessionID%/g, this.opt.sSessionId).replace(/%time%/g, new Date().getTime()), '', this.onSuggestionReceived);
 
 	return true;
 }

+ 1 - 1
Themes/default/scripts/topic.js

@@ -242,7 +242,7 @@ QuickModify.prototype.modifyMsg = function (iMessageId)
 
 	// Send out the XMLhttp request to get more info
 	ajax_indicator(true);
-	sendXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=quotefast;quote=' + iMessageId + ';modify;xml', this.onMessageReceived);
+	sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + 'action=quotefast;quote=' + iMessageId + ';modify;xml', '', this.onMessageReceived);
 }
 
 // The callback function used for the XMLhttp request retrieving the message.

+ 2 - 2
index.php

@@ -194,7 +194,7 @@ function smf_main()
 		fatal_lang_error('not_a_topic', false);
 
 	$no_stat_actions = array('dlattach', 'findmember', 'jsoption', 'requestmembers', 'smstats', '.xml', 'xmlhttp', 'verificationcode', 'viewquery', 'viewsmfile');
-	call_integration_hook('integrate_pre_log_stats', $no_stat_actions);
+	call_integration_hook('integrate_pre_log_stats', array(&$no_stat_actions));
 	// Do some logging, unless this is an attachment, avatar, toggle of editor buttons, theme option, XML feed etc.
 	if (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], $no_stat_actions))
 	{
@@ -235,7 +235,7 @@ function smf_main()
 		if (empty($board) && empty($topic))
 		{
 			$call = '';
-			call_integration_hook('integrate_default_action', $call);
+			call_integration_hook('integrate_default_action', array(&$call));
 			$call = strpos($call, '::') !== false ? explode('::', $call) : $call;
 			if (!empty($call) && is_callable($call))
 				return $call;

+ 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');