Selaa lähdekoodia

Fixed conflicts

Jeremy D 11 vuotta sitten
vanhempi
commit
2a7cea57b6
100 muutettua tiedostoa jossa 3732 lisäystä ja 1403 poistoa
  1. 1 0
      .gitignore
  2. 12 1
      README.md
  3. 64 15
      SSI.php
  4. 13 15
      Sources/Admin.php
  5. 799 0
      Sources/Attachments.php
  6. 4 4
      Sources/BoardIndex.php
  7. 11 11
      Sources/Calendar.php
  8. 37 16
      Sources/Class-BrowserDetect.php
  9. 28 17
      Sources/Class-CurlFetchWeb.php
  10. 1 1
      Sources/Class-Graphics.php
  11. 1 1
      Sources/Class-Package.php
  12. 1 1
      Sources/DbExtra-mysql.php
  13. 1 1
      Sources/DbExtra-postgresql.php
  14. 1 1
      Sources/DbExtra-sqlite.php
  15. 2 2
      Sources/DbPackages-mysql.php
  16. 1 1
      Sources/DbPackages-postgresql.php
  17. 1 1
      Sources/DbPackages-sqlite.php
  18. 1 1
      Sources/DbSearch-mysql.php
  19. 1 1
      Sources/DbSearch-postgresql.php
  20. 1 1
      Sources/DbSearch-sqlite.php
  21. 35 17
      Sources/Display.php
  22. 873 0
      Sources/Drafts.php
  23. 8 8
      Sources/DumpDatabase.php
  24. 1 1
      Sources/Errors.php
  25. 4 86
      Sources/Groups.php
  26. 2 2
      Sources/Help.php
  27. 1 1
      Sources/Karma.php
  28. 87 63
      Sources/Load.php
  29. 24 6
      Sources/LogInOut.php
  30. 2 1
      Sources/Logging.php
  31. 483 85
      Sources/ManageAttachments.php
  32. 1 1
      Sources/ManageBans.php
  33. 1 1
      Sources/ManageBoards.php
  34. 2 2
      Sources/ManageCalendar.php
  35. 5 5
      Sources/ManageErrors.php
  36. 5 3
      Sources/ManageLanguages.php
  37. 1 1
      Sources/ManageMail.php
  38. 58 17
      Sources/ManageMaintenance.php
  39. 1 5
      Sources/ManageMembergroups.php
  40. 16 18
      Sources/ManageMembers.php
  41. 5 4
      Sources/ManageNews.php
  42. 6 9
      Sources/ManagePaid.php
  43. 7 1
      Sources/ManagePermissions.php
  44. 7 3
      Sources/ManagePosts.php
  45. 1 1
      Sources/ManageRegistration.php
  46. 1 1
      Sources/ManageScheduledTasks.php
  47. 54 49
      Sources/ManageSearch.php
  48. 4 4
      Sources/ManageSearchEngines.php
  49. 20 39
      Sources/ManageServer.php
  50. 137 68
      Sources/ManageSettings.php
  51. 2 2
      Sources/ManageSmileys.php
  52. 23 18
      Sources/Memberlist.php
  53. 32 14
      Sources/MessageIndex.php
  54. 3 18
      Sources/ModerationCenter.php
  55. 1 1
      Sources/Modlog.php
  56. 1 1
      Sources/MoveTopic.php
  57. 4 4
      Sources/News.php
  58. 1 1
      Sources/Notify.php
  59. 1 1
      Sources/PackageGet.php
  60. 61 34
      Sources/Packages.php
  61. 68 14
      Sources/PersonalMessage.php
  62. 4 4
      Sources/Poll.php
  63. 91 252
      Sources/Post.php
  64. 4 4
      Sources/PostModeration.php
  65. 1 1
      Sources/Printpage.php
  66. 1 1
      Sources/Profile-Actions.php
  67. 23 4
      Sources/Profile-Modify.php
  68. 28 22
      Sources/Profile-View.php
  69. 14 4
      Sources/Profile.php
  70. 3 3
      Sources/QueryString.php
  71. 7 7
      Sources/Recent.php
  72. 11 19
      Sources/Register.php
  73. 2 2
      Sources/Reminder.php
  74. 44 24
      Sources/RemoveTopic.php
  75. 1 1
      Sources/RepairBoards.php
  76. 13 13
      Sources/Reports.php
  77. 50 8
      Sources/ScheduledTasks.php
  78. 54 46
      Sources/Search.php
  79. 6 6
      Sources/SearchAPI-Custom.php
  80. 54 59
      Sources/SearchAPI-Fulltext.php
  81. 1 1
      Sources/SearchAPI-Standard.php
  82. 25 14
      Sources/Security.php
  83. 1 1
      Sources/SendTopic.php
  84. 1 1
      Sources/Session.php
  85. 8 8
      Sources/SplitTopics.php
  86. 5 5
      Sources/Stats.php
  87. 29 15
      Sources/Subs-Admin.php
  88. 33 18
      Sources/Subs-Auth.php
  89. 33 5
      Sources/Subs-BoardIndex.php
  90. 38 58
      Sources/Subs-Boards.php
  91. 42 29
      Sources/Subs-Calendar.php
  92. 24 17
      Sources/Subs-Categories.php
  93. 1 1
      Sources/Subs-Charset.php
  94. 1 1
      Sources/Subs-Compat.php
  95. 4 4
      Sources/Subs-Db-mysql.php
  96. 1 1
      Sources/Subs-Db-postgresql.php
  97. 2 2
      Sources/Subs-Db-sqlite.php
  98. 30 31
      Sources/Subs-Editor.php
  99. 7 7
      Sources/Subs-Graphics.php
  100. 5 3
      Sources/Subs-List.php

+ 1 - 0
.gitignore

@@ -4,3 +4,4 @@ db_last_error.php
 cache/data*.php
 Packages/backups/*
 Packages/temp
+*.*~

+ 12 - 1
README.md

@@ -9,9 +9,14 @@ Contributions to documentation are licensed under [CC-by-SA 3](http://creativeco
 
 Feel free to fork this repository and make your desired changes.
 
-Please see the [Developer's Certificate of Origin](https://github.com/Spuds/playpen/blob/master/DCO.txt) in the repository:
+Please see the [Developer's Certificate of Origin](https://github.com/SimpleMachines/SMF2.1/blob/master/DCO.txt) in the repository:
 by signing off your contributions, you acknowledge that you can and do license your submissions under the license of the project.
 
+######Branches organization:
+* ***master*** - is the main branch, only used to merge in a "final release"
+* ***development*** - is the branch where the development of the "next" version/s happens
+* ***release-2.1*** - is the branch where bug fixes for the version 2.1 are applied
+
 ######How to contribute:
 * fork the repository. If you are not used to Github, please check out [fork a repository](http://help.github.com/fork-a-repo).
 * branch your repository, to commit the desired changes.
@@ -19,5 +24,11 @@ by signing off your contributions, you acknowledge that you can and do license y
  * an easy way to do so, is to define an alias for the git commit command, which includes -s switch (reference: [How to create Git aliases](http://githacks.com/post/1168909216/how-to-create-git-aliases))
 * send a pull request to us.
 
+######How to submit a pull request:
+* If you want to send a bug fix for the version 2.1, send it to the branch ***release-2.1***
+* If you want to send a new feature, use the branch ***development***
+* You should never send any pull request against the master branch
+For more informations, the ideal branching we would like to follow is the one described in [this article](http://nvie.com/posts/a-successful-git-branching-model/)
+
 Finally, feel free to play around. That's what we're doing. ;)
 

+ 64 - 15
SSI.php

@@ -5,7 +5,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -147,6 +147,10 @@ loadPermissions();
 // Load the current or SSI theme. (just use $ssi_theme = id_theme;)
 loadTheme(isset($ssi_theme) ? (int) $ssi_theme : 0);
 
+// @todo: probably not the best place, but somewhere it should be set...
+if (!headers_sent())
+	header('Content-Type: text/html; charset=' . (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set']));
+
 // Take care of any banning that needs to be done.
 if (isset($_REQUEST['ssi_ban']) || (isset($ssi_ban) && $ssi_ban === true))
 	is_not_banned();
@@ -297,10 +301,13 @@ function ssi_recentPosts($num_recent = 8, $exclude_boards = null, $include_board
 }
 
 // Fetch a post with a particular ID. By default will only show if you have permission to the see the board in question - this can be overriden.
-function ssi_fetchPosts($post_ids, $override_permissions = false, $output_method = 'echo')
+function ssi_fetchPosts($post_ids = array(), $override_permissions = false, $output_method = 'echo')
 {
 	global $user_info, $modSettings;
 
+	if (empty($post_ids))
+		return;
+
 	// Allow the user to request more than one - why not?
 	$post_ids = is_array($post_ids) ? $post_ids : array($post_ids);
 
@@ -319,7 +326,7 @@ function ssi_fetchPosts($post_ids, $override_permissions = false, $output_method
 }
 
 // This removes code duplication in other queries - don't call it direct unless you really know what you're up to.
-function ssi_queryPosts($query_where = '', $query_where_params = array(), $query_limit = '', $query_order = 'm.id_msg DESC', $output_method = 'echo', $limit_body = false)
+function ssi_queryPosts($query_where = '', $query_where_params = array(), $query_limit = 10, $query_order = 'm.id_msg DESC', $output_method = 'echo', $limit_body = false, $override_permissions = false)
 {
 	global $context, $settings, $scripturl, $txt, $db_prefix, $user_info;
 	global $modSettings, $smcFunc;
@@ -336,11 +343,15 @@ function ssi_queryPosts($query_where = '', $query_where_params = array(), $query
 			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . (!$user_info['is_guest'] ? '
 			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = m.id_topic AND lt.id_member = {int:current_member})
 			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = m.id_board AND lmr.id_member = {int:current_member})' : '') . '
-		' . (empty($query_where) ? '' : 'WHERE ' . $query_where) . '
+		WHERE 1=1 ' . ($override_permissions ? '' : '
+			AND {query_wanna_see_board}') . ($modSettings['postmod_active'] ? '
+			AND m.approved = {int:is_approved}' : '') . '
+		' . (empty($query_where) ? '' : 'AND ' . $query_where) . '
 		ORDER BY ' . $query_order . '
 		' . ($query_limit == '' ? '' : 'LIMIT ' . $query_limit),
 		array_merge($query_where_params, array(
 			'current_member' => $user_info['id'],
+			'is_approved' => 1,
 		))
 	);
 	$posts = array();
@@ -792,8 +803,11 @@ function ssi_randomMember($random_type = '', $output_method = 'echo')
 }
 
 // Fetch a specific member.
-function ssi_fetchMember($member_ids, $output_method = 'echo')
+function ssi_fetchMember($member_ids = array(), $output_method = 'echo')
 {
+	if (empty($member_ids))
+		return;
+
 	// Can have more than one member if you really want...
 	$member_ids = is_array($member_ids) ? $member_ids : array($member_ids);
 
@@ -810,8 +824,11 @@ function ssi_fetchMember($member_ids, $output_method = 'echo')
 }
 
 // Get all members of a group.
-function ssi_fetchGroupMembers($group_id, $output_method = 'echo')
+function ssi_fetchGroupMembers($group_id = null, $output_method = 'echo')
 {
+	if ($group_id === null)
+		return;
+
 	$query_where = '
 		id_group = {int:id_group}
 		OR id_post_group = {int:id_group}
@@ -825,11 +842,14 @@ function ssi_fetchGroupMembers($group_id, $output_method = 'echo')
 }
 
 // Fetch some member data!
-function ssi_queryMembers($query_where, $query_where_params = array(), $query_limit = '', $query_order = 'id_member DESC', $output_method = 'echo')
+function ssi_queryMembers($query_where = null, $query_where_params = array(), $query_limit = '', $query_order = 'id_member DESC', $output_method = 'echo')
 {
 	global $context, $settings, $scripturl, $txt, $db_prefix, $user_info;
 	global $modSettings, $smcFunc, $memberContext;
 
+	if ($query_where === null)
+		return;
+
 	// Fetch the members in question.
 	$request = $smcFunc['db_query']('', '
 		SELECT id_member
@@ -892,6 +912,9 @@ function ssi_boardStats($output_method = 'echo')
 {
 	global $db_prefix, $txt, $scripturl, $modSettings, $smcFunc;
 
+	if (!allowedTo('view_stats'))
+		return;
+
 	$totals = array(
 		'members' => $modSettings['totalMembers'],
 		'posts' => $modSettings['totalMessages'],
@@ -1451,6 +1474,9 @@ function ssi_quickSearch($output_method = 'echo')
 {
 	global $scripturl, $txt, $context;
 
+	if (!allowedTo('search_posts'))
+		return;
+
 	if ($output_method != 'echo')
 		return $scripturl . '?action=search';
 
@@ -1476,6 +1502,9 @@ function ssi_todaysBirthdays($output_method = 'echo')
 {
 	global $scripturl, $modSettings, $user_info;
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view') || !allowedTo('profile_view_any'))
+		return;
+
 	$eventOptions = array(
 		'include_birthdays' => true,
 		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
@@ -1495,6 +1524,9 @@ function ssi_todaysHolidays($output_method = 'echo')
 {
 	global $modSettings, $user_info;
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	$eventOptions = array(
 		'include_holidays' => true,
 		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
@@ -1513,6 +1545,9 @@ function ssi_todaysEvents($output_method = 'echo')
 {
 	global $modSettings, $user_info;
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	$eventOptions = array(
 		'include_events' => true,
 		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
@@ -1537,8 +1572,11 @@ function ssi_todaysCalendar($output_method = 'echo')
 {
 	global $modSettings, $txt, $scripturl, $user_info;
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	$eventOptions = array(
-		'include_birthdays' => true,
+		'include_birthdays' => allowedTo('profile_view_any'),
 		'include_holidays' => true,
 		'include_events' => true,
 		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
@@ -1637,11 +1675,13 @@ function ssi_boardNews($board = null, $limit = null, $start = null, $length = nu
 
 	// Find the post ids.
 	$request = $smcFunc['db_query']('', '
-		SELECT id_first_msg
-		FROM {db_prefix}topics
-		WHERE id_board = {int:current_board}' . ($modSettings['postmod_active'] ? '
-			AND approved = {int:is_approved}' : '') . '
-		ORDER BY id_first_msg DESC
+		SELECT t.id_first_msg
+		FROM {db_prefix}topics as t
+		LEFT JOIN {db_prefix}boards as b ON (b.id_board = t.id_board)
+		WHERE t.id_board = {int:current_board}' . ($modSettings['postmod_active'] ? '
+			AND t.approved = {int:is_approved}' : '') . '
+			AND {query_see_board}
+		ORDER BY t.id_first_msg DESC
 		LIMIT ' . $start . ', ' . $limit,
 		array(
 			'current_board' => $board,
@@ -1678,9 +1718,15 @@ function ssi_boardNews($board = null, $limit = null, $start = null, $length = nu
 		if (!empty($length) && $smcFunc['strlen']($row['body']) > $length)
 		{
 			$row['body'] = $smcFunc['substr']($row['body'], 0, $length);
+			$cutoff = false;
 
-			// The first space or line break. (<br />, etc.)
-			$cutoff = max(strrpos($row['body'], ' '), strrpos($row['body'], '<'));
+			$last_space = strrpos($row['body'], ' ');
+			$last_open = strrpos($row['body'], '<');
+			$last_close = strrpos($row['body'], '>');
+			if (empty($last_space) || ($last_space == $last_open + 3 && (empty($last_close) || (!empty($last_close) && $last_close < $last_open))) || $last_space < $last_open || $last_open == $length - 6)
+				$cutoff = $last_open;
+			elseif (empty($last_close) || $last_close < $last_open)
+				$cutoff = $last_space;
 
 			if ($cutoff !== false)
 				$row['body'] = $smcFunc['substr']($row['body'], 0, $cutoff);
@@ -1754,6 +1800,9 @@ function ssi_recentEvents($max_events = 7, $output_method = 'echo')
 {
 	global $db_prefix, $user_info, $scripturl, $modSettings, $txt, $context, $smcFunc;
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	// Find all events which are happening in the near future that the member can see.
 	$request = $smcFunc['db_query']('', '
 		SELECT

+ 13 - 15
Sources/Admin.php

@@ -8,7 +8,7 @@
  * @package SMF
  * @author Simple Machines
  *
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -30,7 +30,7 @@ function AdminMain()
 	// Load the language and templates....
 	loadLanguage('Admin');
 	loadTemplate('Admin', 'admin');
-	loadJavascriptFile('admin.js?alp21', array('default_theme' => true));
+	loadJavascriptFile('admin.js', array('default_theme' => true), 'admin.js');
 
 	// No indexing evil stuff.
 	$context['robot_no_index'] = true;
@@ -39,14 +39,6 @@ function AdminMain()
 
 	// Some preferences.
 	$context['admin_preferences'] = !empty($options['admin_preferences']) ? unserialize($options['admin_preferences']) : array();
-	$context['hooks_exist'] = false;
-	foreach ($modSettings as $key => $setting)
-	if (strpos($key, 'integrate') === 0)
-		if (!empty($setting))
-		{
-			$context['hooks_exist'] = true;
-			break;
-		}
 
 	// Define all the menu structure - see Subs-Menu.php for details!
 	$admin_areas = array(
@@ -189,7 +181,7 @@ function AdminMain()
 					'function' => 'ModifyModSettings',
 					'icon' => 'modifications.png',
 					'subsections' => array(
-						'hooks' => array($txt['hooks_title_list'], 'enabled' => $context['hooks_exist']),
+						'hooks' => array($txt['hooks_title_list']),
 						'general' => array($txt['mods_cat_modifications_misc']),
 						// Mod Authors for a "ADD AFTER" on this line. Ensure you end your change with a comma. For example:
 						// 'shout' => array($txt['shout']),
@@ -227,6 +219,14 @@ function AdminMain()
 						'topics' => array($txt['manageposts_topic_settings']),
 					),
 				),
+				'managedrafts' => array(
+					'label' => $txt['manage_drafts'],
+					'file' => 'Drafts.php',
+					'function' => 'ModifyDraftSettings',
+					'icon' => 'logs.png',
+					'permission' => array('admin_forum'),
+					'enabled' => in_array('dr', $context['admin_features']),
+				),
 				'managecalendar' => array(
 					'label' => $txt['manage_calendar'],
 					'file' => 'ManageCalendar.php',
@@ -276,6 +276,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']),
 					),
 				),
@@ -456,9 +457,6 @@ function AdminMain()
 		}
 	}
 
-	// Let them modify admin areas easily.
-	call_integration_hook('integrate_admin_areas', array(&$admin_areas));
-
 	// Make sure the administrator has a valid session...
 	validateSession();
 
@@ -995,5 +993,5 @@ function AdminEndSession()
 		if (strpos($key, '-admin') !== false)
 			unset($_SESSION['token'][$key]);
 
-	redirectexit('?action=admin');
+	redirectexit('action=admin');
 }

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

+ 4 - 4
Sources/BoardIndex.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -108,14 +108,14 @@ function BoardIndex()
 		$context['show_calendar'] = false;
 
 	$context['page_title'] = sprintf($txt['forum_index'], $context['forum_name']);
-	
+
 	// Mark read button
 	$context['mark_read_button'] = array(
 		'markread' => array('text' => 'mark_as_read', 'image' => 'markread.png', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=all;' . $context['session_var'] . '=' . $context['session_id']),
 	);
-	
+
 	// Allow mods to add additional buttons here
-	call_integration_hook('integrate_mark_read_button'); 
+	call_integration_hook('integrate_mark_read_button');
 }
 
 /**

+ 11 - 11
Sources/Calendar.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -144,12 +144,12 @@ function CalendarMain()
 			'url' => $scripturl . '?action=calendar;viewweek;year=' . $context['current_year'] . ';month=' . $context['current_month'] . ';day=' . $context['current_day'],
 			'name' => $txt['calendar_week'] . ' ' . $context['calendar_grid_main']['week_number']
 		);
-		
+
 	// Build the calendar button array.
 	$context['calendar_buttons'] = array(
 		'post_event' => array('test' => 'can_post', 'text' => 'calendar_post_event', 'image' => 'calendarpe.png', 'lang' => true, 'url' => $scripturl . '?action=calendar;sa=post;month=' . $context['current_month'] . ';year=' . $context['current_year'] . ';' . $context['session_var'] . '=' . $context['session_id']),
 	);
-	
+
 	// Allow mods to add additional buttons here
 	call_integration_hook('integrate_calendar_buttons');
 }
@@ -234,7 +234,7 @@ function CalendarPost()
 				list ($id_board, $id_topic) = $smcFunc['db_fetch_row']($request);
 				$smcFunc['db_free_result']($request);
 			}
-			
+
 			$eventOptions = array(
 				'title' => substr($_REQUEST['evtitle'], 0, 60),
 				'span' => empty($modSettings['cal_allowspan']) || empty($_POST['span']) || $_POST['span'] == 1 || empty($modSettings['cal_maxspan']) || $_POST['span'] > $modSettings['cal_maxspan'] ? 0 : min((int) $modSettings['cal_maxspan'], (int) $_POST['span'] - 1),
@@ -342,7 +342,7 @@ function iCalDownload()
 	// You can't export if the calendar export feature is off.
 	if (empty($modSettings['cal_export']))
 		fatal_lang_error('calendar_export_off', false);
-	
+
 	// Goes without saying that this is required.
 	if (!isset($_REQUEST['eventid']))
 		fatal_lang_error('no_access', false);
@@ -352,7 +352,7 @@ function iCalDownload()
 
 	// Load up the event in question and check it exists.
 	$event = getEventProperties($_REQUEST['eventid']);
-	
+
 	if ($event === false)
 		fatal_lang_error('no_access', false);
 
@@ -387,15 +387,15 @@ function iCalDownload()
 	$filecontents .= 'ORGANIZER;CN="' . $event['realname'] . '":MAILTO:' . $webmaster_email . "\n";
 	$filecontents .= 'DTSTAMP:' . $datestamp . "\n";
 	$filecontents .= 'DTSTART;VALUE=DATE:' . $datestart . "\n";
-	
+
 	// more than one day
 	if ($event['span'] > 1)
 		$filecontents .= 'DTEND;VALUE=DATE:' . $dateend . "\n";
-	
+
 	// event has changed? advance the sequence for this UID
 	if ($event['sequence'] > 0)
 		$filecontents .= 'SEQUENCE:' . $event['sequence'] . "\n";
-	
+
 	$filecontents .= 'SUMMARY:' . implode('', $title);
 	$filecontents .= 'UID:' . $event['eventid'] . '@' . str_replace(' ', '-', $mbname) . "\n";
 	$filecontents .= 'END:VEVENT' . "\n";
@@ -407,7 +407,7 @@ function iCalDownload()
 		@ob_start('ob_gzhandler');
 	else
 		ob_start();
-	
+
 	// Send the file headers
 	header('Pragma: ');
 	header('Cache-Control: no-cache');
@@ -420,7 +420,7 @@ function iCalDownload()
 	header('Content-Disposition: attachment; filename="' . $event['title'] . '.ics"');
 	if (empty($modSettings['enableCompressedOutput']))
 		header('Content-Length: ' . $smcFunc['strlen']($filecontents));
-	
+
 	// This is a calendar item!
 	header('Content-Type: text/calendar');
 

+ 37 - 16
Sources/Class-BrowserDetect.php

@@ -1,20 +1,30 @@
 <?php
 
 /**
- * This class is an experiment for the job of correctly detecting browsers and settings
- * needed for them.
+ * Simple Machines Forum (SMF)
+ *
+ * @package SMF
+ * @author Simple Machines http://www.simplemachines.org
+ * @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...');
+
+/**
+ *  This class is an experiment for the job of correctly detecting browsers and settings needed for them.
  * - Detects the following browsers
  * - Opera, Webkit, Firefox, Web_tv, Konqueror, IE, Gecko
  * - Webkit variants: Chrome, iphone, blackberry, android, safari, ipad, ipod
- * - Opera Versions: 6, 7, 8, 9, 10 and mobile mini and mobi
+ * - Opera Versions: 6, 7, 8 ... 10 ... and mobile mini and mobi
  * - Firefox Versions: 1, 2, 3 .... 11 ...
  * - Chrome Versions: 1 ... 18 ...
- * - IE Versions: 4, 5, 5.5, 6, 7, 8, 9, 10 mobile and Mac
- * - Nokia 
+ * - IE Versions: 4, 5, 5.5, 6, 7, 8, 9, 10 ... mobile and Mac
+ * - Nokia
  */
- 
-if (!defined('SMF'))
-	die('Hacking attempt...');
 
 class browser_detector
 {
@@ -53,7 +63,7 @@ class browser_detector
 		// Old friend, old frenemy
 		elseif ($this->isIe())
 			$this->setupIe();
-		
+
 		// Just a few mobile checks
 		$this->isOperaMini();
 		$this->isOperaMobi();
@@ -122,7 +132,7 @@ class browser_detector
 	function isFirefox()
 	{
 		if (!isset($this->_browsers['is_firefox']))
-			$this->_browsers['is_firefox'] = preg_match('~(?:Firefox|Ice[wW]easel|IceCat|Shiretoko|Minefield)/~', $_SERVER['HTTP_USER_AGENT']) === 1;
+			$this->_browsers['is_firefox'] = preg_match('~(?:Firefox|Ice[wW]easel|IceCat|Shiretoko|Minefield)/~', $_SERVER['HTTP_USER_AGENT']) === 1 && $this->isGecko();
 		return $this->_browsers['is_firefox'];
 	}
 
@@ -319,18 +329,21 @@ class browser_detector
 			$context['browser_body_id'] = 'mobile';
 		else
 		{
+			// add in any specific detection conversions here if you want a special body id e.g. 'is_opera9' => 'opera9'
 			$browser_priority = array(
 				'is_ie6' => 'ie6',
 				'is_ie7' => 'ie7',
+				'is_ie8' => 'ie8',
+				'is_ie9' => 'ie9',
+				'is_ie10' => 'ie10',
 				'is_ie' => 'ie',
-				'is_firefox3' => 'firefox3',
-				'is_firefox4' => 'firefox4',
 				'is_firefox' => 'firefox',
 				'is_chrome' => 'chrome',
 				'is_safari' => 'safari',
-				'is_opera8' => 'opera8',
-				'is_opera9' => 'opera9',
 				'is_opera10' => 'opera10',
+				'is_opera11' => 'opera11',
+				'is_opera12' => 'opera12',
+				'is_opera' => 'opera',
 				'is_konqueror' => 'konqueror',
 			);
 
@@ -354,14 +367,17 @@ class browser_detector
 	function fillInformation()
 	{
 		$this->_browsers += array(
-			'is_webkit' => false,
+			'is_opera' => false,
 			'is_opera6' => false,
 			'is_opera7' => false,
 			'is_opera8' => false,
 			'is_opera9' => false,
 			'is_opera10' => false,
-			'is_ie4' => false,
+			'is_webkit' => false,
 			'is_mac_ie' => false,
+			'is_web_tv' => false,
+			'is_konqueror' => false,
+			'is_firefox' => false,
 			'is_firefox1' => false,
 			'is_firefox2' => false,
 			'is_firefox3' => false,
@@ -369,12 +385,17 @@ class browser_detector
 			'is_android' => false,
 			'is_chrome' => false,
 			'is_safari' => false,
+			'is_gecko'  => false,
 			'is_ie8' => false,
 			'is_ie7' => false,
 			'is_ie6' => false,
 			'is_ie5.5' => false,
 			'is_ie5' => false,
+			'is_ie' => false,
+			'is_ie4' => false,
 			'ie_standards_fix' => false,
+			'needs_size_fix' => false,
+			'possibly_robot' => false,
 		);
 	}
 }

+ 28 - 17
Sources/Class-CurlFetchWeb.php

@@ -1,7 +1,19 @@
 <?php
 /**
- *
  * Simple Machines Forum (SMF)
+ * 
+ * @package SMF
+ * @author Simple Machines http://www.simplemachines.org
+ * @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...');
+
+/**
  * Simple cURL class to fetch a web page
  * Properly redirects even with safe mode and basedir restrictions
  * Can provide simple post options to a page
@@ -18,7 +30,6 @@
  *  - $fetch_data('http://www.simplemachines.org', array('user' => 'name', 'password' => 'password')); // post to a page
  *  - $fetch_data('http://www.simplemachines.org', parameter1&parameter2&parameter3); // post to a page
  *
- *
  * Get the data
  *  - $fetch_data->result('body'); // just the page content
  *  - $fetch_data->result(); // an array of results, body, header, http result codes
@@ -27,7 +38,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -67,7 +78,7 @@ class curl_fetch_web_data
 	}
 
 	/**
-	* Main calling function, 
+	* Main calling function,
 	*  - will request the page data from a given $url
 	*  - optionally will post data to the page form if post data is supplied
 	*  - passed arrays will be converted to a post string joined with &'s
@@ -121,12 +132,12 @@ class curl_fetch_web_data
 		curl_exec($cr);
 
 		// Get what was returned
-		$curl_info		= curl_getinfo($cr);
-		$curl_content	= curl_multi_getcontent($cr);
-		$url			= $curl_info['url']; // Last effective URL
-		$http_code		= $curl_info['http_code']; // Last HTTP code
-		$body			= (!curl_error($cr)) ? substr($curl_content, $curl_info['header_size']) : false;
-		$error			= (curl_error($cr)) ? curl_error($cr) : false;
+		$curl_info = curl_getinfo($cr);
+		$curl_content = curl_multi_getcontent($cr);
+		$url = $curl_info['url']; // Last effective URL
+		$http_code = $curl_info['http_code']; // Last HTTP code
+		$body = (!curl_error($cr)) ? substr($curl_content, $curl_info['header_size']) : false;
+		$error = (curl_error($cr)) ? curl_error($cr) : false;
 
 		// close this request
 		curl_close($cr);
@@ -164,9 +175,9 @@ class curl_fetch_web_data
 
 		// redirect headers are often incomplete or relative so we need to make sure they are fully qualified
 		$new_url_parse['scheme'] = isset($new_url_parse['scheme']) ? $new_url_parse['scheme'] : $last_url_parse['scheme'];
-		$new_url_parse['host']   = isset($new_url_parse['host']) ? $new_url_parse['host'] : $last_url_parse['host'];
-		$new_url_parse['path']   = isset($new_url_parse['path']) ? $new_url_parse['path'] : $last_url_parse['path'];
-		$new_url_parse['query']  = isset($new_url_parse['query']) ? $new_url_parse['query'] : '';
+		$new_url_parse['host'] = isset($new_url_parse['host']) ? $new_url_parse['host'] : $last_url_parse['host'];
+		$new_url_parse['path'] = isset($new_url_parse['path']) ? $new_url_parse['path'] : $last_url_parse['path'];
+		$new_url_parse['query'] = isset($new_url_parse['query']) ? $new_url_parse['query'] : '';
 
 		// Build the new URL that was in the http header
 		return $new_url_parse['scheme'] . '://' . $new_url_parse['host'] . $new_url_parse['path'] . (!empty($new_url_parse['query']) ? '?' . $new_url_parse['query'] : '');
@@ -193,7 +204,7 @@ class curl_fetch_web_data
 
 	/**
 	* Will return all results from all loops (redirects)
-	*  - Can be call as ->result_raw(x) where x is a specific loop results.
+	*  - Can be called as ->result_raw(x) where x is a specific loop results.
 	*  - Call as ->result_raw() for everything.
 	*
 	* @param type $response_number
@@ -223,16 +234,16 @@ class curl_fetch_web_data
 		if (is_array($post_data))
 		{
 			$postvars = array();
-			
+
 			// build the post data, drop ones with leading @'s since those can be used to send files, we don't support that.
 			foreach ($post_data as $name => $value)
 				$postvars[] = $name . '=' . urlencode($value[0] == '@' ? '' : $value);
-			
+
 			return implode('&', $postvars);
 		}
 		else
 			return $post_data;
-		
+
 	}
 
 	/**

+ 1 - 1
Sources/Class-Graphics.php

@@ -13,7 +13,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Class-Package.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbExtra-mysql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbExtra-postgresql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbExtra-sqlite.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 2 - 2
Sources/DbPackages-mysql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -621,7 +621,7 @@ function smf_db_list_indexes($table_name, $detail = false, $parameters = array()
 function smf_db_create_query_column($column)
 {
 	global $smcFunc;
-	
+
 	// Auto increment is easy here!
 	if (!empty($column['auto']))
 	{

+ 1 - 1
Sources/DbPackages-postgresql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbPackages-sqlite.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbSearch-mysql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbSearch-postgresql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbSearch-sqlite.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 35 - 17
Sources/Display.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -143,6 +143,15 @@ function Display()
 		$_SESSION['last_read_topic'] = $topic;
 	}
 
+	$topic_parameters = array(
+		'current_member' => $user_info['id'],
+		'current_topic' => $topic,
+		'current_board' => $board,
+	);
+	$topic_selects = array();
+	$topic_tables = array();
+	call_integration_hook('integrate_display_topic', array(&$topic_selects, &$topic_tables, &$topic_parameters));
+
 	// @todo Why isn't this cached?
 	// @todo if we get id_board in this query and cache it, we can save a query on posting
 	// Get all the important topic info.
@@ -152,17 +161,15 @@ function Display()
 			t.id_member_started, t.id_first_msg, t.id_last_msg, t.approved, t.unapproved_posts, t.id_redirect_topic,
 			' . ($user_info['is_guest'] ? 't.id_last_msg + 1' : 'IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1') . ' AS new_from
 			' . (!empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board ? ', id_previous_board, id_previous_topic' : '') . '
+			' . (!empty($topic_selects) ? implode(',', $topic_selects) : '') . '
 		FROM {db_prefix}topics AS t
 			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)' . ($user_info['is_guest'] ? '' : '
 			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member})
 			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})') . '
+			' . (!empty($topic_tables) ? implode("\n\t", $topic_tables) : '') . '
 		WHERE t.id_topic = {int:current_topic}
 		LIMIT 1',
-		array(
-			'current_member' => $user_info['id'],
-			'current_topic' => $topic,
-			'current_board' => $board,
-		)
+			$topic_parameters
 	);
 	if ($smcFunc['db_num_rows']($request) == 0)
 		fatal_lang_error('not_a_topic', false);
@@ -322,7 +329,7 @@ function Display()
 	}
 
 	// Create a previous next string if the selected theme has it as a selected option.
-	$context['previous_next'] = $modSettings['enablePreviousNext'] ? '<a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=prev#new">' . $txt['previous_next_back'] . '</a> <a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=next#new">' . $txt['previous_next_forward'] . '</a>' : '';
+	$context['previous_next'] = $modSettings['enablePreviousNext'] ? '<a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=prev#new">' . $txt['previous_next_back'] . '</a> - <a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=next#new">' . $txt['previous_next_forward'] . '</a>' : '';
 
 	// Check if spellchecking is both enabled and actually working. (for quick reply.)
 	$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
@@ -480,7 +487,7 @@ function Display()
 			$context['link_moderators'][] = '<a href="' . $scripturl . '?action=profile;u=' . $mod['id'] . '" title="' . $txt['board_moderator'] . '">' . $mod['name'] . '</a>';
 
 		// And show it after the board's name.
-		$context['linktree'][count($context['linktree']) - 2]['extra_after'] = ' (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')';
+		$context['linktree'][count($context['linktree']) - 2]['extra_after'] = '<span class="board_moderators"> (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')</span>';
 	}
 
 	// Information about the current topic...
@@ -962,6 +969,14 @@ function Display()
 				$attachments[$row['id_msg']][] = $row;
 		}
 
+		$msg_parameters = array(
+			'message_list' => $messages,
+			'new_from' => $topicinfo['new_from'],
+		);
+		$msg_selects = array();
+		$msg_tables = array();
+		call_integration_hook('integrate_query_message', array(&$msg_selects, &$msg_tables, &$msg_parameters));
+
 		// What?  It's not like it *couldn't* be only guests in this topic...
 		if (!empty($posters))
 			loadMemberData($posters);
@@ -970,13 +985,12 @@ function Display()
 				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, body,
 				smileys_enabled, poster_name, poster_email, approved,
 				id_msg_modified < {int:new_from} AS is_read
+				' . (!empty($msg_selects) ? implode(',', $msg_selects) : '') . '
 			FROM {db_prefix}messages
+				' . (!empty($msg_tables) ? implode("\n\t", $msg_tables) : '') . '
 			WHERE id_msg IN ({array_int:message_list})
 			ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'),
-			array(
-				'message_list' => $messages,
-				'new_from' => $topicinfo['new_from'],
-			)
+			$msg_parameters
 		);
 
 		// Go to the last message if the given time is beyond the time of the last message.
@@ -1066,6 +1080,12 @@ function Display()
 	$context['can_restore_topic'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_board']);
 	$context['can_restore_msg'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_topic']);
 
+	// Check if the draft functions are enabled and that they have permission to use them (for quick reply.)
+	$context['drafts_save'] = !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft') && $context['can_reply'];
+	$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']) && allowedTo('post_autosave_draft');
+	if (!empty($context['drafts_save']))
+		loadLanguage('Drafts');
+
 	// Wireless shows a "more" if you can do anything special.
 	if (WIRELESS && WIRELESS_PROTOCOL != 'wap')
 	{
@@ -1263,6 +1283,8 @@ function prepareDisplayContext($reset = false)
 	// Is this user the message author?
 	$output['is_message_author'] = $message['id_member'] == $user_info['id'];
 
+	call_integration_hook('integrate_prepare_display_context', array(&$output, &$message));
+
 	if (empty($options['view_newest_first']))
 		$counter++;
 	else
@@ -1408,12 +1430,8 @@ function Download()
 	header('Connection: close');
 	header('ETag: ' . $eTag);
 
-	// IE 6 just doesn't play nice. As dirty as this seems, it works.
-	if (isBrowser('ie6') && isset($_REQUEST['image']))
-		unset($_REQUEST['image']);
-
 	// Make sure the mime type warrants an inline display.
-	elseif (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
+	if (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
 		unset($_REQUEST['image']);
 
 	// Does this have a mime type?

+ 873 - 0
Sources/Drafts.php

@@ -0,0 +1,873 @@
+<?php
+
+/**
+ * This file contains all the functions that allow for the saving,
+ * retrieving, deleting and settings for the drafts function.
+ *
+ * Simple Machines Forum (SMF)
+ *
+ * @package SMF
+ * @author Simple Machines http://www.simplemachines.org
+ * @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...');
+
+loadLanguage('Drafts');
+
+/**
+ * Saves a post draft in the user_drafts table
+ * The core draft feature must be enabled, as well as the post draft option
+ * Determines if this is a new or an existing draft
+ *
+ * @return boolean
+ */
+function SaveDraft(&$post_errors)
+{
+	global $txt, $context, $user_info, $smcFunc, $modSettings, $board;
+
+	// can you be, should you be ... here?
+	if (empty($modSettings['drafts_enabled']) || empty($modSettings['drafts_post_enabled']) || !allowedTo('post_draft') || !isset($_POST['save_draft']) || !isset($_POST['id_draft']))
+		return false;
+
+	// read in what they sent us, if anything
+	$id_draft = (int) $_POST['id_draft'];
+	$draft_info = ReadDraft($id_draft);
+
+	// prepare any data from the form
+	$topic_id = empty($_REQUEST['topic']) ? 0 : (int) $_REQUEST['topic'];
+	$draft['icon'] = empty($_POST['icon']) ? 'xx' : preg_replace('~[\./\\\\*:"\'<>]~', '', $_POST['icon']);
+	$draft['smileys_enabled'] = isset($_POST['ns']) ? (int) $_POST['ns'] : 0;
+	$draft['locked'] = isset($_POST['lock']) ? (int) $_POST['lock'] : 0;
+	$draft['sticky'] = isset($_POST['sticky']) && !empty($modSettings['enableStickyTopics']) ? (int) $_POST['sticky'] : 0;
+	$draft['subject'] = strtr($smcFunc['htmlspecialchars']($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
+	$draft['body'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES);
+
+	// message and subject still need a bit more work
+	preparsecode($draft['body']);
+	if ($smcFunc['strlen']($draft['subject']) > 100)
+		$draft['subject'] = $smcFunc['substr']($draft['subject'], 0, 100);
+
+	// Modifying an existing draft, like hitting the save draft button or autosave enabled?
+	if (!empty($id_draft) && !empty($draft_info) && $draft_info['id_member'] == $user_info['id'])
+	{
+		$smcFunc['db_query']('', '
+			UPDATE {db_prefix}user_drafts
+			SET
+				id_topic = {int:id_topic},
+				id_board = {int:id_board},
+				poster_time = {int:poster_time},
+				subject = {string:subject},
+				smileys_enabled = {int:smileys_enabled},
+				body = {string:body},
+				icon = {string:icon},
+				locked = {int:locked},
+				is_sticky = {int:is_sticky}
+			WHERE id_draft = {int:id_draft}',
+			array (
+				'id_topic' => $topic_id,
+				'id_board' => $board,
+				'poster_time' => time(),
+				'subject' => $draft['subject'],
+				'smileys_enabled' => (int) $draft['smileys_enabled'],
+				'body' => $draft['body'],
+				'icon' => $draft['icon'],
+				'locked' => $draft['locked'],
+				'is_sticky' => $draft['sticky'],
+				'id_draft' => $id_draft,
+			)
+		);
+
+		// some items to return to the form
+		$context['draft_saved'] = true;
+		$context['id_draft'] = $id_draft;
+
+		// cleanup
+		unset($_POST['save_draft']);
+	}
+	// otherwise creating a new draft
+	else
+	{
+		$smcFunc['db_insert']('',
+			'{db_prefix}user_drafts',
+			array(
+				'id_topic' => 'int',
+				'id_board' => 'int',
+				'type' => 'int',
+				'poster_time' => 'int',
+				'id_member' => 'int',
+				'subject' => 'string-255',
+				'smileys_enabled' => 'int',
+				'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : 'string-65534'),
+				'icon' => 'string-16',
+				'locked' => 'int',
+				'is_sticky' => 'int'
+			),
+			array(
+				$topic_id,
+				$board,
+				0,
+				time(),
+				$user_info['id'],
+				$draft['subject'],
+				$draft['smileys_enabled'],
+				$draft['body'],
+				$draft['icon'],
+				$draft['locked'],
+				$draft['sticky']
+			),
+			array(
+				'id_draft'
+			)
+		);
+
+		// get the id of the new draft
+		$id_draft = $smcFunc['db_insert_id']('{db_prefix}user_drafts', 'id_draft');
+
+		// everything go as expected?
+		if (!empty($id_draft))
+		{
+			$context['draft_saved'] = true;
+			$context['id_draft'] = $id_draft;
+		}
+		else
+			$post_errors[] = 'draft_not_saved';
+
+		// cleanup
+		unset($_POST['save_draft']);
+	}
+
+	// if we were called from the autosave function, send something back
+	if (!empty($id_draft) && isset($_REQUEST['xml']) && (!in_array('session_timeout', $post_errors)))
+		XmlDraft($id_draft);
+
+	return true;
+}
+
+/**
+ * Saves a PM draft in the user_drafts table
+ * The core draft feature must be enable, as well as the pm draft option
+ * Determines if this is a new or and update to an existing draft
+ *
+ * @global type $context
+ * @global type $user_info
+ * @global type $smcFunc
+ * @global type $modSettings
+ * @param string $post_errors
+ * @param type $recipientList
+ * @return boolean
+ */
+function SavePMDraft(&$post_errors, $recipientList)
+{
+	global $context, $user_info, $smcFunc, $modSettings;
+
+	// PM survey says ... can you stay or must you go
+	if (empty($modSettings['drafts_enabled']) || empty($modSettings['drafts_pm_enabled']) || !allowedTo('pm_draft') || !isset($_POST['save_draft']))
+		return false;
+
+	// read in what you sent us
+	$id_pm_draft = (int) $_POST['id_pm_draft'];
+	$draft_info = ReadDraft($id_pm_draft, 1);
+
+	// determine who this is being sent to
+	if (isset($_REQUEST['xml']))
+	{
+		$recipientList['to'] = isset($_POST['recipient_to']) ? explode(',', $_POST['recipient_to']) : array();
+		$recipientList['bcc'] = isset($_POST['recipient_bcc']) ? explode(',', $_POST['recipient_bcc']) : array();
+	}
+	elseif (!empty($draft_info['to_list']) && empty($recipientList))
+		$recipientList = unserialize($draft_info['to_list']);
+
+	// prepare the data we got from the form
+	$reply_id = empty($_POST['replied_to']) ? 0 : (int) $_POST['replied_to'];
+	$outbox = empty($_POST['outbox']) ? 0 : 1;
+	$draft['body'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES);
+	$draft['subject'] = strtr($smcFunc['htmlspecialchars']($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
+
+	// message and subject still need a bit more massaging
+	preparsecode($draft['body']);
+	if ($smcFunc['strlen']($draft['subject']) > 100)
+		$draft['subject'] = $smcFunc['substr']($draft['subject'], 0, 100);
+
+	// Modifying an existing PM draft?
+	if (!empty($id_pm_draft) && !empty($draft_info) && $draft_info['id_member'] == $user_info['id'])
+	{
+		$smcFunc['db_query']('', '
+			UPDATE {db_prefix}user_drafts
+			SET id_reply = {int:id_reply},
+				type = {int:type},
+				poster_time = {int:poster_time},
+				subject = {string:subject},
+				body = {string:body},
+				to_list = {string:to_list},
+				outbox = {int:outbox}
+			WHERE id_draft = {int:id_pm_draft}
+			LIMIT 1',
+			array(
+				'id_reply' => $reply_id,
+				'type' => 1,
+				'poster_time' =>  time(),
+				'subject' =>  $draft['subject'],
+				'body' => $draft['body'],
+				'id_pm_draft' => $id_pm_draft,
+				'to_list' => serialize($recipientList),
+				'outbox' => $outbox,
+			)
+		);
+
+		// some items to return to the form
+		$context['draft_saved'] = true;
+		$context['id_pm_draft'] = $id_pm_draft;
+	}
+	// otherwise creating a new PM draft.
+	else
+	{
+		$smcFunc['db_insert']('',
+			'{db_prefix}user_drafts',
+			array(
+				'id_reply' => 'int',
+				'type' => 'int',
+				'poster_time' => 'int',
+				'id_member' => 'int',
+				'subject' => 'string-255',
+				'body' => 'string-65534',
+				'to_list' => 'string-255',
+				'outbox' => 'int',
+			),
+			array(
+				$reply_id,
+				1,
+				time(),
+				$user_info['id'],
+				$draft['subject'],
+				$draft['body'],
+				serialize($recipientList),
+				$outbox,
+			),
+			array(
+				'id_draft'
+			)
+		);
+
+		// get the new id
+		$id_pm_draft = $smcFunc['db_insert_id']('{db_prefix}user_drafts', 'id_draft');
+
+		// everything go as expected, if not toss an error
+		if (!empty($id_pm_draft))
+		{
+			$context['draft_saved'] = true;
+			$context['id_pm_draft'] = $id_pm_draft;
+		}
+		else
+			$post_errors[] = 'draft_not_saved';
+	}
+
+	// if we were called from the autosave function, send something back
+	if (!empty($id_pm_draft) && isset($_REQUEST['xml']) && !in_array('session_timeout', $post_errors))
+		XmlDraft($id_pm_draft);
+
+	return;
+}
+
+/**
+ * Reads a draft in from the user_drafts table
+ * Only loads the draft of a given type 0 for post, 1 for pm draft
+ * validates that the draft is the users draft
+ * Optionally loads the draft in to context or superglobal for loading in to the form
+ *
+ * @param type $id_draft - draft to load
+ * @param type $type - type of draft
+ * @param type $check - validate the user
+ * @param type $load - load it for use in a form
+ * @return boolean
+ */
+function ReadDraft($id_draft, $type = 0, $check = true, $load = false)
+{
+	global $context, $user_info, $smcFunc, $modSettings;
+
+	// always clean to be sure
+	$id_draft = (int) $id_draft;
+	$type = (int) $type;
+
+	// nothing to read, nothing to do
+	if (empty($id_draft))
+		return false;
+
+	// load in this draft from the DB
+	$request = $smcFunc['db_query']('', '
+		SELECT *
+		FROM {db_prefix}user_drafts
+		WHERE id_draft = {int:id_draft}' . ($check ? '
+			AND id_member = {int:id_member}' : '') . '
+			AND type = {int:type}' . (!empty($modSettings['drafts_keep_days']) ? '
+			AND poster_time > {int:time}' : '') . '
+		LIMIT 1',
+		array(
+			'id_member' => $user_info['id'],
+			'id_draft' => $id_draft,
+			'type' => $type,
+			'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
+		)
+	);
+
+	// no results?
+	if (!$smcFunc['db_num_rows']($request))
+		return false;
+
+	// load up the data
+	$draft_info = $smcFunc['db_fetch_assoc']($request);
+	$smcFunc['db_free_result']($request);
+
+	// Load it up for the templates as well
+	$recipients = array();
+	if (!empty($load))
+	{
+		if ($type === 0)
+		{
+			// a standard post draft?
+			$context['sticky'] = !empty($draft_info['is_sticky']) ? $draft_info['is_sticky'] : '';
+			$context['locked'] = !empty($draft_info['locked']) ? $draft_info['locked'] : '';
+			$context['use_smileys'] = !empty($draft_info['smileys_enabled']) ? true : false;
+			$context['icon'] = !empty($draft_info['icon']) ? $draft_info['icon'] : 'xx';
+			$context['message'] = !empty($draft_info['body']) ? str_replace('<br />', "\n", un_htmlspecialchars(stripslashes($draft_info['body']))) : '';
+			$context['subject'] = !empty($draft_info['subject']) ? stripslashes($draft_info['subject']) : '';
+			$context['board'] = !empty($draft_info['board_id']) ? $draft_info['id_board'] : '';
+			$context['id_draft'] = !empty($draft_info['id_draft']) ? $draft_info['id_draft'] : 0;
+		}
+		elseif ($type === 1)
+		{
+			// one of those pm drafts? then set it up like we have an error
+			$_REQUEST['outbox'] = !empty($draft_info['outbox']);
+			$_REQUEST['subject'] = !empty($draft_info['subject']) ? stripslashes($draft_info['subject']) : '';
+			$_REQUEST['message'] = !empty($draft_info['body']) ? str_replace('<br />', "\n", un_htmlspecialchars(stripslashes($draft_info['body']))) : '';
+			$_REQUEST['replied_to'] = !empty($draft_info['id_reply']) ? $draft_info['id_reply'] : 0;
+			$context['id_pm_draft'] = !empty($draft_info['id_draft']) ? $draft_info['id_draft'] : 0;
+			$recipients = unserialize($draft_info['to_list']);
+
+			// make sure we only have integers in this array
+			$recipients['to'] = array_map('intval', $recipients['to']);
+			$recipients['bcc'] = array_map('intval', $recipients['bcc']);
+
+			// pretend we messed up to populate the pm message form
+			messagePostError(array(), array(), $recipients);
+			return true;
+		}
+	}
+
+	return $draft_info;
+}
+
+/**
+ * Deletes one or many drafts from the DB
+ * Validates the drafts are from the user
+ * is supplied an array of drafts will attempt to remove all of them
+ *
+ * @param type $id_draft
+ * @param type $check
+ * @return boolean
+ */
+function DeleteDraft($id_draft, $check = true)
+{
+	global $user_info, $smcFunc;
+
+	// Only a single draft.
+	if (is_numeric($id_draft))
+		$id_draft = array($id_draft);
+
+	// can't delete nothing
+	if (empty($id_draft) || ($check && empty($user_info['id'])))
+		return false;
+
+	$smcFunc['db_query']('', '
+		DELETE FROM {db_prefix}user_drafts
+		WHERE id_draft IN ({array_int:id_draft})' . ($check ? '
+			AND  id_member = {int:id_member}' : ''),
+		array (
+			'id_draft' => $id_draft,
+			'id_member' => empty($user_info['id']) ? -1 : $user_info['id'],
+		)
+	);
+}
+
+/**
+ * Loads in a group of drafts for the user of a given type (0/posts, 1/pm's)
+ * loads a specific draft for forum use if selected.
+ * Used in the posting screens to allow draft selection
+ * WIll load a draft if selected is supplied via post
+ *
+ * @param type $member_id
+ * @param type $topic
+ * @param type $draft_type
+ * @return boolean
+ */
+function ShowDrafts($member_id, $topic = false, $draft_type = 0)
+{
+	global $smcFunc, $scripturl, $context, $txt, $modSettings;
+
+	// Permissions
+	if (($draft_type === 0 && empty($context['drafts_save'])) || ($draft_type === 1 && empty($context['drafts_pm_save'])) || empty($member_id))
+		return false;
+
+	$context['drafts'] = array();
+
+	// has a specific draft has been selected?  Load it up if there is not a message already in the editor
+	if (isset($_REQUEST['id_draft']) && empty($_POST['subject']) && empty($_POST['message']))
+		ReadDraft((int) $_REQUEST['id_draft'], $draft_type, true, true);
+
+	// load the drafts this user has available
+	$request = $smcFunc['db_query']('', '
+		SELECT *
+		FROM {db_prefix}user_drafts
+		WHERE id_member = {int:id_member}' . ((!empty($topic) && empty($draft_type)) ? '
+			AND id_topic = {int:id_topic}' : (!empty($topic) ? '
+			AND id_reply = {int:id_topic}' : '')) . '
+			AND type = {int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
+			AND poster_time > {int:time}' : '') . '
+		ORDER BY poster_time DESC',
+		array(
+			'id_member' => $member_id,
+			'id_topic' => (int) $topic,
+			'draft_type' => $draft_type,
+			'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
+		)
+	);
+
+	// add them to the draft array for display
+	while ($row = $smcFunc['db_fetch_assoc']($request))
+	{
+		// Post drafts
+		if ($draft_type === 0)
+			$context['drafts'][] = array(
+				'subject' => censorText(shorten_subject(stripslashes($row['subject']), 24)),
+				'poster_time' => timeformat($row['poster_time']),
+				'link' => '<a href="' . $scripturl . '?action=post;board=' . $row['id_board'] . ';' . (!empty($row['id_topic']) ? 'topic='. $row['id_topic'] .'.0;' : '') . 'id_draft=' . $row['id_draft'] . '">' . $row['subject'] . '</a>',
+			);
+		// PM drafts
+		elseif ($draft_type === 1)
+			$context['drafts'][] = array(
+				'subject' => censorText(shorten_subject(stripslashes($row['subject']), 24)),
+				'poster_time' => timeformat($row['poster_time']),
+				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;id_draft=' . $row['id_draft'] . '">' . (!empty($row['subject']) ? $row['subject'] : $txt['drafts_none']) . '</a>',
+			);
+	}
+	$smcFunc['db_free_result']($request);
+}
+
+/**
+ * Returns an xml response to an autosave ajax request
+ * provides the id of the draft saved and the time it was saved
+ *
+ * @param type $id_draft
+ */
+function XmlDraft($id_draft)
+{
+	global $txt, $context;
+
+	header('Content-Type: text/xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
+
+	echo '<?xml version="1.0" encoding="', $context['character_set'], '"?>
+	<drafts>
+		<draft id="', $id_draft, '"><![CDATA[', $txt['draft_saved_on'], ': ', timeformat(time()), ']]></draft>
+	</drafts>';
+
+	obExit(false);
+}
+
+/**
+ * Show all drafts of a given type by the current user
+ * Uses the showdraft template
+ * Allows for the deleting and loading/editing of drafts
+ *
+ * @param type $memID
+ * @param type $draft_type
+ */
+function showProfileDrafts($memID, $draft_type = 0)
+{
+	global $txt, $user_info, $scripturl, $modSettings, $context, $smcFunc;
+
+	// Some initial context.
+	$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
+	$context['current_member'] = $memID;
+
+	// If just deleting a draft, do it and then redirect back.
+	if (!empty($_REQUEST['delete']))
+	{
+		checkSession('get');
+		$id_delete = (int) $_REQUEST['delete'];
+
+		$smcFunc['db_query']('', '
+			DELETE FROM {db_prefix}user_drafts
+			WHERE id_draft = {int:id_draft}
+				AND id_member = {int:id_member}
+				AND type = {int:draft_type}
+			LIMIT 1',
+			array(
+				'id_draft' => $id_delete,
+				'id_member' => $memID,
+				'draft_type' => $draft_type,
+			)
+		);
+
+		redirectexit('action=profile;u=' . $memID . ';area=showdrafts;start=' . $context['start']);
+	}
+
+	// Default to 10.
+	if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
+		$_REQUEST['viewscount'] = '10';
+
+	// Get the count of applicable drafts on the boards they can (still) see ...
+	// @todo .. should we just let them see their drafts even if they have lost board access ?
+	$request = $smcFunc['db_query']('', '
+		SELECT COUNT(id_draft)
+		FROM {db_prefix}user_drafts AS ud
+			INNER JOIN {db_prefix}boards AS b ON (b.id_board = ud.id_board AND {query_see_board})
+		WHERE id_member = {int:id_member}
+			AND type={int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
+			AND poster_time > {int:time}' : ''),
+		array(
+			'id_member' => $memID,
+			'draft_type' => $draft_type,
+			'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
+		)
+	);
+	list ($msgCount) = $smcFunc['db_fetch_row']($request);
+	$smcFunc['db_free_result']($request);
+
+	$maxIndex = (int) $modSettings['defaultMaxMessages'];
+
+	// Make sure the starting place makes sense and construct our friend the page index.
+	$context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showdrafts', $context['start'], $msgCount, $maxIndex);
+	$context['current_page'] = $context['start'] / $maxIndex;
+
+	// Reverse the query if we're past 50% of the pages for better performance.
+	$start = $context['start'];
+	$reverse = $_REQUEST['start'] > $msgCount / 2;
+	if ($reverse)
+	{
+		$maxIndex = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : (int) $modSettings['defaultMaxMessages'];
+		$start = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 || $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] ? 0 : $msgCount - $context['start'] - $modSettings['defaultMaxMessages'];
+	}
+
+	// Find this user's drafts for the boards they can access
+	// @todo ... do we want to do this?  If they were able to create a draft, do we remove thier access to said draft if they loose
+	//           access to the board or if the topic moves to a board they can not see?
+	$request = $smcFunc['db_query']('', '
+		SELECT
+			b.id_board, b.name AS bname,
+			ud.id_member, ud.id_draft, ud.body, ud.smileys_enabled, ud.subject, ud.poster_time, ud.icon, ud.id_topic, ud.locked, ud.is_sticky
+		FROM {db_prefix}user_drafts AS ud
+			INNER JOIN {db_prefix}boards AS b ON (b.id_board = ud.id_board AND {query_see_board})
+		WHERE ud.id_member = {int:current_member}
+			AND type = {int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
+			AND poster_time > {int:time}' : '') . '
+		ORDER BY ud.id_draft ' . ($reverse ? 'ASC' : 'DESC') . '
+		LIMIT ' . $start . ', ' . $maxIndex,
+		array(
+			'current_member' => $memID,
+			'draft_type' => $draft_type,
+			'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
+		)
+	);
+
+	// Start counting at the number of the first message displayed.
+	$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
+	$context['posts'] = array();
+	while ($row = $smcFunc['db_fetch_assoc']($request))
+	{
+		// Censor....
+		if (empty($row['body']))
+			$row['body'] = '';
+
+		$row['subject'] = $smcFunc['htmltrim']($row['subject']);
+		if (empty($row['subject']))
+			$row['subject'] = $txt['no_subject'];
+
+		censorText($row['body']);
+		censorText($row['subject']);
+
+		// BBC-ilize the message.
+		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], 'draft' . $row['id_draft']);
+
+		// And the array...
+		$context['drafts'][$counter += $reverse ? -1 : 1] = array(
+			'body' => $row['body'],
+			'counter' => $counter,
+			'alternate' => $counter % 2,
+			'board' => array(
+				'name' => $row['bname'],
+				'id' => $row['id_board']
+			),
+			'topic' => array(
+				'id' => $row['id_topic'],
+				'link' => empty($row['id']) ? $row['subject'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['subject'] . '</a>',
+			),
+			'subject' => $row['subject'],
+			'time' => timeformat($row['poster_time']),
+			'timestamp' => forum_time(true, $row['poster_time']),
+			'icon' => $row['icon'],
+			'id_draft' => $row['id_draft'],
+			'locked' => $row['locked'],
+			'sticky' => $row['is_sticky'],
+		);
+	}
+	$smcFunc['db_free_result']($request);
+
+	// All drafts were retrieved in reverse order, get them right again.
+	if ($reverse)
+		$context['drafts'] = array_reverse($context['drafts'], true);
+
+	$context['sub_template'] = 'showDrafts';
+}
+
+/**
+ * Show all PM drafts of the current user
+ * Uses the showpmdraft template
+ * Allows for the deleting and loading/editing of drafts
+ *
+ * @param type $memID
+ * @param type $draft_type
+ */
+function showPMDrafts($memID = -1)
+{
+	global $txt, $user_info, $scripturl, $modSettings, $context, $smcFunc;
+
+	// init
+	$draft_type = 1;
+
+	// If just deleting a draft, do it and then redirect back.
+	if (!empty($_REQUEST['delete']))
+	{
+		checkSession('get');
+		$id_delete = (int) $_REQUEST['delete'];
+		$start = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
+
+		$smcFunc['db_query']('', '
+			DELETE FROM {db_prefix}user_drafts
+			WHERE id_draft = {int:id_draft}
+				AND id_member = {int:id_member}
+				AND type = {int:draft_type}
+			LIMIT 1',
+			array(
+				'id_draft' => $id_delete,
+				'id_member' => $memID,
+				'draft_type' => $draft_type,
+			)
+		);
+
+		// now redirect back to the list
+		redirectexit('action=pm;sa=showpmdrafts;start=' . $start);
+	}
+
+	// perhaps a draft was selected for editing? if so pass this off
+	if (!empty($_REQUEST['id_draft']) && !empty($context['drafts_pm_save']) && $memID == $user_info['id'])
+	{
+		checkSession('get');
+		$id_draft = (int) $_REQUEST['id_draft'];
+		redirectexit('action=pm;sa=send;id_draft=' . $id_draft);
+	}
+
+	// Default to 10.
+	if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
+		$_REQUEST['viewscount'] = '10';
+
+	// Get the count of applicable drafts
+	$request = $smcFunc['db_query']('', '
+		SELECT COUNT(id_draft)
+		FROM {db_prefix}user_drafts AS ud
+		WHERE id_member = {int:id_member}
+			AND type={int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
+			AND poster_time > {int:time}' : ''),
+		array(
+			'id_member' => $memID,
+			'draft_type' => $draft_type,
+			'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
+		)
+	);
+	list ($msgCount) = $smcFunc['db_fetch_row']($request);
+	$smcFunc['db_free_result']($request);
+
+	$maxIndex = (int) $modSettings['defaultMaxMessages'];
+
+	// Make sure the starting place makes sense and construct our friend the page index.
+	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=showpmdrafts', $context['start'], $msgCount, $maxIndex);
+	$context['current_page'] = $context['start'] / $maxIndex;
+
+	// Reverse the query if we're past 50% of the total for better performance.
+	$start = $context['start'];
+	$reverse = $_REQUEST['start'] > $msgCount / 2;
+	if ($reverse)
+	{
+		$maxIndex = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : (int) $modSettings['defaultMaxMessages'];
+		$start = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 || $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] ? 0 : $msgCount - $context['start'] - $modSettings['defaultMaxMessages'];
+	}
+
+	// Load in this user's PM drafts
+	$request = $smcFunc['db_query']('', '
+		SELECT
+			ud.id_member, ud.id_draft, ud.body, ud.subject, ud.poster_time, ud.outbox, ud.id_reply, ud.to_list
+		FROM {db_prefix}user_drafts AS ud
+		WHERE ud.id_member = {int:current_member}
+			AND type = {int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
+			AND poster_time > {int:time}' : '') . '
+		ORDER BY ud.id_draft ' . ($reverse ? 'ASC' : 'DESC') . '
+		LIMIT ' . $start . ', ' . $maxIndex,
+		array(
+			'current_member' => $memID,
+			'draft_type' => $draft_type,
+			'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
+		)
+	);
+
+	// Start counting at the number of the first message displayed.
+	$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
+	$context['posts'] = array();
+	while ($row = $smcFunc['db_fetch_assoc']($request))
+	{
+		// Censor....
+		if (empty($row['body']))
+			$row['body'] = '';
+
+		$row['subject'] = $smcFunc['htmltrim']($row['subject']);
+		if (empty($row['subject']))
+			$row['subject'] = $txt['no_subject'];
+
+		censorText($row['body']);
+		censorText($row['subject']);
+
+		// BBC-ilize the message.
+		$row['body'] = parse_bbc($row['body'], true, 'draft' . $row['id_draft']);
+
+		// Have they provide who this will go to?
+		$recipients = array(
+			'to' => array(),
+			'bcc' => array(),
+		);
+		$recipient_ids = (!empty($row['to_list'])) ? unserialize($row['to_list']) : array();
+
+		// @todo ... this is a bit ugly since it runs an extra query for every message, do we want this?
+		// at least its only for draft PM's and only the user can see them ... so not heavily used .. still
+		if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
+		{
+			$recipient_ids['to'] = array_map('intval', $recipient_ids['to']);
+			$recipient_ids['bcc'] = array_map('intval', $recipient_ids['bcc']);
+			$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
+
+			$request_2 = $smcFunc['db_query']('', '
+				SELECT id_member, real_name
+				FROM {db_prefix}members
+				WHERE id_member IN ({array_int:member_list})',
+				array(
+					'member_list' => $allRecipients,
+				)
+			);
+			while ($result = $smcFunc['db_fetch_assoc']($request_2))
+			{
+				$recipientType = in_array($result['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
+				$recipients[$recipientType][] = $result['real_name'];
+			}
+			$smcFunc['db_free_result']($request_2);
+		}
+
+		// Add the items to the array for template use
+		$context['drafts'][$counter += $reverse ? -1 : 1] = array(
+			'body' => $row['body'],
+			'counter' => $counter,
+			'alternate' => $counter % 2,
+			'subject' => $row['subject'],
+			'time' => timeformat($row['poster_time']),
+			'timestamp' => forum_time(true, $row['poster_time']),
+			'id_draft' => $row['id_draft'],
+			'recipients' => $recipients,
+			'age' => floor((time() - $row['poster_time']) / 86400),
+			'remaining' => (!empty($modSettings['drafts_keep_days']) ? floor($modSettings['drafts_keep_days'] - ((time() - $row['poster_time']) / 86400)) : 0),
+		);
+	}
+	$smcFunc['db_free_result']($request);
+
+	// if the drafts were retrieved in reverse order, then put them in the right order again.
+	if ($reverse)
+		$context['drafts'] = array_reverse($context['drafts'], true);
+
+	// off to the template we go
+	$context['page_title'] = $txt['drafts'];
+	$context['sub_template'] = 'showPMDrafts';
+	$context['linktree'][] = array(
+		'url' => $scripturl . '?action=pm;sa=showpmdrafts',
+		'name' => $txt['drafts'],
+	);
+}
+
+/**
+ * Modify any setting related to drafts.
+ * Requires the admin_forum permission.
+ * Accessed from ?action=admin;area=managedrafts
+ *
+ * @param bool $return_config = false
+ * @uses Admin template, edit_topic_settings sub-template.
+ */
+function ModifyDraftSettings($return_config = false)
+{
+	global $context, $txt, $sourcedir, $scripturl;
+
+	isAllowedTo('admin_forum');
+
+	// Here are all the draft settings, a bit lite for now, but we can add more :P
+	$config_vars = array(
+			// Draft settings ...
+			array('check', 'drafts_post_enabled'),
+			array('check', 'drafts_pm_enabled'),
+			array('check', 'drafts_show_saved_enabled', 'subtext' => $txt['drafts_show_saved_enabled_subnote']),
+			array('int', 'drafts_keep_days', 'postinput' => $txt['days_word'], 'subtext' => $txt['drafts_keep_days_subnote']),
+			'',
+			array('check', 'drafts_autosave_enabled', 'subtext' => $txt['drafts_autosave_enabled_subnote']),
+			array('int', 'drafts_autosave_frequency', 'postinput' => $txt['manageposts_seconds'], 'subtext' => $txt['drafts_autosave_frequency_subnote']),
+	);
+
+	if ($return_config)
+		return $config_vars;
+
+	// Get the settings template ready.
+	require_once($sourcedir . '/ManageServer.php');
+
+	// Setup the template.
+	$context['page_title'] = $txt['managedrafts_settings'];
+	$context['sub_template'] = 'show_settings';
+
+	// Saving them ?
+	if (isset($_GET['save']))
+	{
+		checkSession();
+
+		// Protect them from themselves.
+		$_POST['drafts_autosave_frequency'] = $_POST['drafts_autosave_frequency'] < 30 ? 30 : $_POST['drafts_autosave_frequency'];
+		saveDBSettings($config_vars);
+		redirectexit('action=admin;area=managedrafts');
+	}
+	
+	// some javascript to enable / disable the frequency input box
+	$context['settings_post_javascript'] = '
+		var autosave = document.getElementById(\'drafts_autosave_enabled\');
+		createEventListener(autosave)
+		autosave.addEventListener(\'change\', toggle);
+		toggle();
+
+		function toggle()
+		{
+			var select_elem = document.getElementById(\'drafts_autosave_frequency\');
+			select_elem.disabled = !autosave.checked;
+		}
+	';
+
+	// Final settings...
+	$context['post_url'] = $scripturl . '?action=admin;area=managedrafts;save';
+	$context['settings_title'] = $txt['managedrafts_settings'];
+
+	// Prepare the settings...
+	prepareDBSettingContext($config_vars);
+}
+
+?>

+ 8 - 8
Sources/DumpDatabase.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -50,7 +50,7 @@ function DumpDatabase2()
 	@set_time_limit(600);
 	$time_limit = ini_get('max_execution_time');
 	$start_time = time();
-	
+
 	// @todo ... fail on not getting the requested memory?
 	setMemoryLimit('256M');
 	$memory_limit = memoryReturnBytes(ini_get('memory_limit')) / 4;
@@ -113,7 +113,7 @@ function DumpDatabase2()
 	$crlf = "\r\n";
 
 	// SQL Dump Header.
-	$db_chunks = 
+	$db_chunks =
 		'-- ==========================================================' . $crlf .
 		'--' . $crlf .
 		'-- Database dump of tables in `' . $db_name . '`' . $crlf .
@@ -141,7 +141,7 @@ function DumpDatabase2()
 		// Are we dumping the structures?
 		if (isset($_REQUEST['struct']))
 		{
-			$db_chunks .= 
+			$db_chunks .=
 				$crlf .
 				'--' . $crlf .
 				'-- Table structure for table `' . $tableName . '`' . $crlf .
@@ -177,7 +177,7 @@ function DumpDatabase2()
 
 			if ($first_round)
 			{
-				$db_chunks .= 
+				$db_chunks .=
 					$crlf .
 					'--' . $crlf .
 					'-- Dumping data in `' . $tableName . '`' . $crlf .
@@ -185,7 +185,7 @@ function DumpDatabase2()
 					$crlf;
 				$first_round = false;
 			}
-			$db_chunks .= 
+			$db_chunks .=
 				$get_rows;
 			$current_used_memory += $smcFunc['strlen']($db_chunks);
 
@@ -205,11 +205,11 @@ function DumpDatabase2()
 
 		// No rows to get - skip it.
 		if ($close_table)
-			$db_backup .= 
+			$db_backup .=
 			'-- --------------------------------------------------------' . $crlf;
 	}
 
-	$db_backup .= 
+	$db_backup .=
 		$crlf .
 		'-- Done' . $crlf;
 

+ 1 - 1
Sources/Errors.php

@@ -9,7 +9,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 4 - 86
Sources/Groups.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -67,91 +67,8 @@ function Groups()
  */
 function GroupList()
 {
-	global $txt, $scripturl, $user_profile, $user_info, $context, $settings, $modSettings, $smcFunc, $sourcedir;
+	global $txt, $context, $sourcedir;
 
-	// Yep, find the groups...
-	$request = $smcFunc['db_query']('', '
-		SELECT mg.id_group, mg.group_name, mg.description, mg.group_type, mg.online_color, mg.hidden,
-			mg.icons, IFNULL(gm.id_member, 0) AS can_moderate
-		FROM {db_prefix}membergroups AS mg
-			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
-		WHERE mg.min_posts = {int:min_posts}
-			AND mg.id_group != {int:mod_group}' . (allowedTo('admin_forum') ? '' : '
-			AND mg.group_type != {int:is_protected}') . '
-		ORDER BY group_name',
-		array(
-			'current_member' => $user_info['id'],
-			'min_posts' => -1,
-			'mod_group' => 3,
-			'is_protected' => 1,
-		)
-	);
-	// This is where we store our groups.
-	$context['groups'] = array();
-	$group_ids = array();
-	$context['can_moderate'] = allowedTo('manage_membergroups');
-	while ($row = $smcFunc['db_fetch_assoc']($request))
-	{
-		// We only list the groups they can see.
-		if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
-			continue;
-
-		$row['icons'] = explode('#', $row['icons']);
-
-		$context['groups'][$row['id_group']] = array(
-			'id' => $row['id_group'],
-			'name' => $row['group_name'],
-			'desc' => $row['description'],
-			'color' => $row['online_color'],
-			'type' => $row['group_type'],
-			'num_members' => 0,
-			'icons' => !empty($row['icons'][0]) && !empty($row['icons'][1]) ? str_repeat('<img src="' . $settings['images_url'] . '/' . $row['icons'][1] . '" alt="*" />', $row['icons'][0]) : '',
-		);
-
-		$context['can_moderate'] |= $row['can_moderate'];
-		$group_ids[] = $row['id_group'];
-	}
-	$smcFunc['db_free_result']($request);
-
-	// Count up the members separately...
-	if (!empty($group_ids))
-	{
-		$query = $smcFunc['db_query']('', '
-			SELECT id_group, COUNT(*) AS num_members
-			FROM {db_prefix}members
-			WHERE id_group IN ({array_int:group_list})
-			GROUP BY id_group',
-			array(
-				'group_list' => $group_ids,
-			)
-		);
-		while ($row = $smcFunc['db_fetch_assoc']($query))
-			$context['groups'][$row['id_group']]['num_members'] += $row['num_members'];
-		$smcFunc['db_free_result']($query);
-
-		// Only do additional groups if we can moderate...
-		if ($context['can_moderate'])
-		{
-			$query = $smcFunc['db_query']('', '
-				SELECT mg.id_group, COUNT(*) AS num_members
-				FROM {db_prefix}membergroups AS mg
-					INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_screen}
-						AND mem.id_group != mg.id_group
-						AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0)
-				WHERE mg.id_group IN ({array_int:group_list})
-				GROUP BY mg.id_group',
-				array(
-					'group_list' => $group_ids,
-					'blank_screen' => '',
-				)
-			);
-			while ($row = $smcFunc['db_fetch_assoc']($query))
-				$context['groups'][$row['id_group']]['num_members'] += $row['num_members'];
-			$smcFunc['db_free_result']($query);
-		}
-	}
-
-	$context['sub_template'] = 'group_index';
 	$context['page_title'] = $txt['viewing_groups'];
 
 	// Making a list is not hard with this beauty.
@@ -373,6 +290,7 @@ function list_getGroupCount()
  * It allows sorting on several columns.
  * It redirects to itself.
  * @uses ManageMembergroups template, group_members sub template.
+ * @todo: use createList
  */
 function MembergroupMembers()
 {
@@ -914,7 +832,7 @@ function GroupRequests()
 						<option value="reject">' . $txt['mc_groupr_reject'] . '</option>
 						<option value="reason">' . $txt['mc_groupr_reject_w_reason'] . '</option>
 					</select>
-					<input type="submit" name="go" value="' . $txt['go'] . '" onclick="var sel = document.getElementById(\'req_action\'); if (sel.value != 0 &amp;&amp; sel.value != \'reason\' &amp;&amp; !confirm(\'' . $txt['mc_groupr_warning'] . '\')) return false;" class="button_submit" />',
+					<input type="submit" name="go" value="' . $txt['go'] . '" onclick="var sel = document.getElementById(\'req_action\'); if (sel.value != 0 &amp;&amp; sel.value != \'reason\' &amp;&amp; !confirm(\'' . $txt['mc_groupr_warning'] . '\')) return false;" class="button_submit" style="float: none"/>',
 				'align' => 'right',
 			),
 		),

+ 2 - 2
Sources/Help.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -84,7 +84,7 @@ function ShowAdminHelp()
 		loadLanguage('ManagePermissions');
 
 	loadTemplate('Help');
-	
+
 	// Allow mods to load their own language file here
  	call_integration_hook('integrate_helpadmin');
 

+ 1 - 1
Sources/Karma.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 87 - 63
Sources/Load.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -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'))
 	{
@@ -940,7 +951,7 @@ function loadMemberData($users, $is_name = false, $set = 'normal')
 
 	// Allow mods to easily add to the selected member data
 	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables));
-	
+
 	if (!empty($users))
 	{
 		// Load the member's data.
@@ -1645,7 +1656,7 @@ function loadTheme($id_theme = 0, $initialize = true)
 		// If not a user variant, select the default.
 		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
 			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
-	
+
 		// Do this to keep things easier in the templates.
 		$context['theme_variant'] = '_' . $context['theme_variant'];
 		$context['theme_variant_url'] = $context['theme_variant'] . '/';
@@ -1658,7 +1669,6 @@ function loadTheme($id_theme = 0, $initialize = true)
 	// Allow overriding the board wide time/number formats.
 	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
 		$user_info['time_format'] = $txt['time_format'];
-	$txt['number_format'] = empty($txt['number_format']) ? empty($modSettings['number_format']) ? '' : $modSettings['number_format'] : $txt['number_format'];
 
 	if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'always')
 	{
@@ -1676,10 +1686,6 @@ function loadTheme($id_theme = 0, $initialize = true)
 
 	$context['tabindex'] = 1;
 
-	// Fix font size with HTML 4.01, etc.
-	if (isset($settings['doctype']))
-		$context['browser']['needs_size_fix'] |= $settings['doctype'] == 'html' && isBrowser('ie6');
-
 	// Compatibility.
 	if (!isset($settings['theme_version']))
 		$modSettings['memberCount'] = $modSettings['totalMembers'];
@@ -1688,7 +1694,6 @@ function loadTheme($id_theme = 0, $initialize = true)
 	$context['admin_features'] = isset($modSettings['admin_features']) ? explode(',', $modSettings['admin_features']) : array('cd,cp,k,w,rg,ml,pm');
 
 	// Default JS variables for use in every theme
-	loadLanguage('index');
 	$context['javascript_vars'] = array(
 		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
 		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
@@ -1704,23 +1709,23 @@ function loadTheme($id_theme = 0, $initialize = true)
 		'ajax_notification_cancel_text' => JavaScriptEscape($txt['modify_cancel']),
 		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
 	);
-	
+
 	// Add the JQuery library to the list of files to load.
 	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');
-	// Auto load, eh? template_javascript() will take care of the inline half of this.
+		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', 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', 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())
 	{
@@ -1786,7 +1791,7 @@ function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
 {
 	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
 
-	// Do any style sheets first (reroute to the new function to do this!)
+	// Do any style sheets first, cause we're easy with those.
 	if (!empty($style_sheets))
 	{
 		if (!is_array($style_sheets))
@@ -1891,76 +1896,96 @@ function loadSubTemplate($sub_template_name, $fatal = false)
  * Add a CSS file for output later
  *
  * @param string $filename
- * @param array $options
- * 	- 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
+ * @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;
 
-	$theme = !empty($options['default_theme']) ? 'default_theme' : 'theme';
-	$options['force_current'] = !empty($options['force_current']) ? $options['force_current'] : false;
+	$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']))
 	{
-		// Make sure it exists, too!
-		if(!file_exists($settings[$theme . '_dir'] . '/css/' . $filename))
+		// Are we validating the the file exists?
+		if (!empty($params['validate']) && !file_exists($settings[$theme . '_dir'] . '/css/' . $filename))
 		{
 			// Maybe the default theme has it?
-			if($theme == 'theme' && file_exists($settings['default_theme_dir'] . '/' . $filename) && !$options['force_current'])
-				$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']);
 	}
 
-	if(!empty($filename))
-		$context['css_files'][(empty($id) ? basename($filename) : $id)] = array('filename' => $filename, 'options' => $options);
+	// Add it to the array for use in the template
+	if (!empty($filename))
+		$context['css_files'][$id] = array('filename' => $filename, 'options' => $params);
 }
 
 /**
  * Add a Javascript file for output later
- *
+ 
  * @param string $filename
- * @param array $options, possible parameters:
- * 	- 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
- * 	- defer (true/false): define if the file should load in <head> or before the closing <html> tag
- *	- async (true/false): if the script should be loaded asynchronously (HTML5)
+ * @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
+ *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
+ *
  * @param string $id
  */
-function loadJavascriptFile($filename, $options = array(), $id = '')
+function loadJavascriptFile($filename, $params = array(), $id = '')
 {
 	global $settings, $context;
 
-	$theme = !empty($options['default_theme']) ? 'default_theme' : 'theme';
-	$options['force_current'] = !empty($options['force_current']) ? $options['force_current'] : false;
+	$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']))
 	{
-		// Make sure it exists, too!
-		if(!file_exists($settings[$theme . '_dir'] . '/scripts/' . $filename))
+		// Are we validating it exists on disk?
+		if (!empty($params['validate']) && !file_exists($settings[$theme . '_dir'] . '/scripts/' . $filename))
 		{
-			// Maybe the default theme has it?
-			if($theme == 'theme' && file_exists($settings['default_theme_dir'] . '/' . $filename) && !$options['force_current'])
-				$filename = $settings['default_theme_url'] . '/scripts/' . $filename;
+			// can't find it in this theme, how about the default?
+			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']);
 	}
 
-	if(!empty($filename))
-		$context['javascript_files'][(empty($id) ? basename($filename) : $id)] = array('filename' => $filename, 'options' => $options);
+	// Add it to the array for use in the template
+	if (!empty($filename))
+		$context['javascript_files'][$id] = array('filename' => $filename, 'options' => $params);
 }
 
 /**
@@ -1975,15 +2000,16 @@ function addJavascriptVar($key, $value, $escape = false)
 {
 	global $context;
 
-	if(!empty($key) && !empty($value))
-		$context['javascript_vars'][$key] = $escape ? JavaScriptEscape($value) : $value;
+	if (!empty($key) && !empty($value))
+		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
 }
 
 /**
  * Add a block of inline Javascript code to be executed later
- * Only use this if you have to, generally external JS files are better, but for very small scripts
- * or for scripts that require help from PHP/whatever, this can be useful.
- * Do note that all code added with this function is added to the same <script> tag so do make sure your JS is clean!
+ *
+ * - only use this if you have to, generally external JS files are better, but for very small scripts
+ *   or for scripts that require help from PHP/whatever, this can be useful.
+ * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
  *
  * @param string $javascript
  * @param bool $defer = false, define if the script should load in <head> or before the closing <html> tag
@@ -1992,8 +2018,8 @@ function addInlineJavascript($javascript, $defer = false)
 {
 	global $context;
 
-	if(!empty($javascript))
-		$context['javascript_inline'][$defer ? 'defer' : 'standard'][] = $javascript;
+	if (!empty($javascript))
+		$context['javascript_inline'][(!empty($defer) ? 'defer' : 'standard')][] = $javascript;
 }
 
 /**
@@ -2431,9 +2457,7 @@ function template_include($filename, $once = false)
 				$data2 = preg_split('~\<br( /)?\>~', $data2);
 
 				// Fix the PHP code stuff...
-				if (isBrowser('ie4') || isBrowser('ie5') || isBrowser('ie5.5'))
-					$data2 = str_replace("\t", '<pre style="display: inline;">' . "\t" . '</pre>', $data2);
-				elseif (!isBrowser('gecko'))
+				if (!isBrowser('gecko'))
 					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
 				else
 					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
@@ -2590,8 +2614,8 @@ function cache_quick_get($key, $file, $function, $params, $level = 1)
 /**
  * Puts value in the cache under key for ttl seconds.
  *
- * - It may "miss" so shouldn't be depended on 
- * - Uses the cahce engine chosen in the ACP and saved in settings.php
+ * - It may "miss" so shouldn't be depended on
+ * - Uses the cache engine chosen in the ACP and saved in settings.php
  * - It supports:
  *     Turck MMCache: http://turck-mmcache.sourceforge.net/index_old.html#api
  *     Xcache: http://xcache.lighttpd.net/wiki/XcacheApi

+ 24 - 6
Sources/LogInOut.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -28,7 +28,11 @@ if (!defined('SMF'))
  */
 function Login()
 {
-	global $txt, $context, $scripturl;
+	global $txt, $context, $scripturl, $user_info;
+
+	// You are already logged in, go take a tour of the boards
+	if (!empty($user_info['id']))
+		redirectexit();
 
 	// In wireless?  If so, use the correct sub template.
 	if (WIRELESS)
@@ -115,6 +119,11 @@ function Login2()
 		// Some whitelisting for login_url...
 		if (empty($_SESSION['login_url']))
 			redirectexit();
+		elseif (!empty($_SESSION['login_url']) && (strpos('http://', $_SESSION['login_url']) === false && strpos('https://', $_SESSION['login_url']) === false))
+		{
+			unset ($_SESSION['login_url']);
+			redirectexit();
+		}
 		else
 		{
 			// Best not to clutter the session data too much...
@@ -292,7 +301,7 @@ function Login2()
 		$other_passwords = array();
 
 		// None of the below cases will be used most of the time (because the salt is normally set.)
-		if ($user_settings['password_salt'] == '')
+		if (!empty($modSettings['enable_password_conversion']) && $user_settings['password_salt'] == '')
 		{
 			// YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, IkonBoard, and none at all.
 			$other_passwords[] = crypt($_POST['passwrd'], substr($_POST['passwrd'], 0, 2));
@@ -318,10 +327,10 @@ function Login2()
 			$other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5'));
 		}
 		// The hash should be 40 if it's SHA-1, so we're safe with more here too.
-		elseif (strlen($user_settings['passwd']) == 32)
+		elseif (!empty($modSettings['enable_password_conversion']) && strlen($user_settings['passwd']) == 32)
 		{
 			// vBulletin 3 style hashing?  Let's welcome them with open arms \o/.
-			$other_passwords[] = md5(md5($_POST['passwrd']) . $user_settings['password_salt']);
+			$other_passwords[] = md5(md5($_POST['passwrd']) . stripslashes($user_settings['password_salt']));
 
 			// Hmm.. p'raps it's Invision 2 style?
 			$other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
@@ -336,7 +345,8 @@ function Login2()
 			$other_passwords[] = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
 
 			// BurningBoard3 style of hashing.
-			$other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd'])));
+			if (!empty($modSettings['enable_password_conversion']))
+				$other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd'])));
 
 			// Perhaps we converted to UTF-8 and have a valid password being hashed differently.
 			if ($context['character_set'] == 'utf8' && !empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] != 'utf8')
@@ -358,6 +368,9 @@ function Login2()
 			$other_passwords[] = sha1_smf(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
 		}
 
+		// Allows mods to easily extend the $other_passwords array
+		call_integration_hook('integrate_other_passwords', array($other_passwords));
+
 		// Whichever encryption it was using, let's make it use SMF's now ;).
 		if (in_array($user_settings['passwd'], $other_passwords))
 		{
@@ -607,6 +620,11 @@ function Logout($internal = false, $redirect = true)
 	{
 		if (empty($_SESSION['logout_url']))
 			redirectexit('', $context['server']['needs_login_fix']);
+		elseif (!empty($_SESSION['logout_url']) && (strpos('http://', $_SESSION['logout_url']) === false && strpos('https://', $_SESSION['logout_url']) === false))
+		{
+			unset ($_SESSION['logout_url']);
+			redirectexit();
+		}
 		else
 		{
 			$temp = $_SESSION['logout_url'];

+ 2 - 1
Sources/Logging.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -247,6 +247,7 @@ function displayDebug()
 	', $txt['debug_subtemplates'], count($context['debug']['sub_templates']), ': <em>', implode('</em>, <em>', $context['debug']['sub_templates']), '</em>.<br />
 	', $txt['debug_language_files'], count($context['debug']['language_files']), ': <em>', implode('</em>, <em>', $context['debug']['language_files']), '</em>.<br />
 	', $txt['debug_stylesheets'], count($context['debug']['sheets']), ': <em>', implode('</em>, <em>', $context['debug']['sheets']), '</em>.<br />
+	', $txt['debug_hooks'], empty($context['debug']['hooks']) ? 0 : count($context['debug']['hooks']) . ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_hooks\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_hooks" style="display: none;"><em>' . implode('</em>, <em>', $context['debug']['hooks']), '</em></span>)', '<br />
 	', $txt['debug_files_included'], count($files), ' - ', round($total_size / 1024), $txt['debug_kb'], ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_include_info\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_include_info" style="display: none;"><em>', implode('</em>, <em>', $files), '</em></span>)<br />';
 
 	// What tokens are active?

+ 483 - 85
Sources/ManageAttachments.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -78,7 +78,7 @@ function ManageAttachments()
 /**
  * Allows to show/change attachment settings.
  * This is the default sub-action of the 'Attachments and Avatars' center.
- * Called by index.php?action=admin;area=manageattachments;sa=attachements.
+ * Called by index.php?action=admin;area=manageattachments;sa=attachments.
  *
  * @param bool $return_config = false
  * @uses 'attachments' sub template.
@@ -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);
@@ -282,7 +359,7 @@ function BrowseFiles()
 	// Set the options for the list component.
 	$listOptions = array(
 		'id' => 'file_list',
-		'title' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ( $context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments'))],
+		'title' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ($context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments'))],
 		'items_per_page' => $modSettings['defaultMaxMessages'],
 		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
 		'default_sort_col' => 'name',
@@ -408,11 +485,8 @@ function BrowseFiles()
 					'value' => $txt['downloads'],
 				),
 				'data' => array(
-					'function' => create_function('$rowData','
-						global $txt;
-
-						return comma_format($rowData[\'downloads\']);
-					'),
+					'db' => 'downloads',
+					'comma_format' => true,
 				),
 				'sort' => array(
 					'default' => 'a.downloads',
@@ -1008,7 +1082,7 @@ function RepairAttachments()
 	@set_time_limit(600);
 
 	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
-	$_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
+	$context['starting_substep'] = $_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
 
 	// Don't recall the session just in case.
 	if ($_GET['step'] == 0 && $_GET['substep'] == 0)
@@ -1461,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;
@@ -1512,7 +1578,7 @@ function RepairAttachments()
 								);
 								if ($smcFunc['db_num_rows']($request) == 0)
 								{
-									if ($fix_errors)
+									if ($fix_errors && in_array('files_without_attachment', $to_fix))
 									{
 										@unlink($attach_dir . '/' . $file);
 									}
@@ -1525,8 +1591,21 @@ function RepairAttachments()
 								$smcFunc['db_free_result']($request);
 							}
 						}
+						elseif ($file != 'index.php')
+						{
+							if ($fix_errors && in_array('files_without_attachment', $to_fix))
+							{
+								@unlink($attach_dir . '/' . $file);
+							}
+							else
+							{
+								$context['repair_errors']['files_without_attachment']++;
+								$to_fix[] = 'files_without_attachment';
+							}
+						}
 					}
 					$current_check++;
+					$_GET['substep'] = $current_check;
 					if ($current_check - $files_checked >= $max_checks)
 						pauseAttachmentMaintenance($to_fix);
 				}
@@ -1568,7 +1647,7 @@ function pauseAttachmentMaintenance($to_fix, $max_substep = 0)
 		@apache_reset_timeout();
 
 	// Have we already used our maximum time?
-	if (time() - array_sum(explode(' ', $time_start)) < 3)
+	if (time() - array_sum(explode(' ', $time_start)) < 3 || $context['starting_substep'] == $_GET['substep'])
 		return;
 
 	$context['continue_get_data'] = '?action=admin;area=manageattachments;sa=repair' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id'];
@@ -1773,13 +1852,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)
 		{
@@ -1787,33 +1877,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);
+
+					// 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'];
 
-				list ($num_attach) = $smcFunc['db_fetch_row']($request);
-				$smcFunc['db_free_result']($request);
+					// 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'])));
+					}
 
-				// It's safe to delete.
-				if ($num_attach == 0)
-					continue;
+					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)
@@ -1832,31 +2021,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',
@@ -1868,13 +2114,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(
@@ -1883,9 +2129,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(
@@ -1925,14 +2171,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';
 }
@@ -1944,23 +2274,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();
@@ -1971,15 +2305,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>' : ''),
 		);
 	}
 
@@ -1997,23 +2349,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())
@@ -2022,20 +2423,17 @@ 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);
 }
 

+ 1 - 1
Sources/ManageBans.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/ManageBoards.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 2 - 2
Sources/ManageCalendar.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -162,7 +162,7 @@ function ModifyHolidays()
 			array(
 				'position' => 'below_table_data',
 				'value' => '
-					
+
 					<input type="submit" name="delete" value="' . $txt['quickmod_delete_selected'] . '" class="button_submit" />
 					<a class="button_link" href="' . $scripturl . '?action=admin;area=managecalendar;sa=editholiday" style="margin: 0 1em">' . $txt['holidays_add'] . '</a>',
 				'style' => 'text-align: right;',

+ 5 - 5
Sources/ManageErrors.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -148,7 +148,7 @@ function ViewErrorLog()
 				'file' => $row['file'],
 				'line' => $row['line'],
 				'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($row['file']) . ';line=' . $row['line'],
-				'link' => $linkfile ? '<a href="' . $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($row['file']) . ';line=' . $row['line'] . '" onclick="return reqWin(this.href, 600, 400, false);">' . $row['file'] . '</a>' : $row['file'],
+				'link' => $linkfile ? '<a href="' . $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($row['file']) . ';line=' . $row['line'] . '" onclick="return reqWin(this.href, 600, 480, false);">' . $row['file'] . '</a>' : $row['file'],
 				'search' => base64_encode($row['file']),
 			);
 		}
@@ -324,9 +324,9 @@ function deleteErrors()
 /**
  * View a file specified in $_REQUEST['file'], with php highlighting on it
  * Preconditions:
- * file must be readable,
- * full file path must be base64 encoded,
- * user must have admin_forum permission.
+ *  - file must be readable,
+ *  - full file path must be base64 encoded,
+ *  - user must have admin_forum permission.
  * The line number number is specified by $_REQUEST['line']...
  * The function will try to get the 20 lines before and after the specified line.
  */

+ 5 - 3
Sources/ManageLanguages.php

@@ -8,7 +8,7 @@
  * @package SMF
  * @author Simple Machines
  *
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -147,9 +147,11 @@ function list_getLanguagesList()
 	require_once($sourcedir . '/Class-Package.php');
 	$language_list = new xmlArray(fetch_web_data($url), true);
 
-	// Check it exists.
-	if (!$language_list->exists('languages/language'))
+	// Check that the site responded and that the language exists.
+	if (!$language_list->exists('languages'))
 		$context['smf_error'] = 'no_response';
+	elseif (!$language_list->exists('languages/language'))
+		$context['smf_error'] = 'no_files';
 	else
 	{
 		$language_list = $language_list->path('languages[0]');

+ 1 - 1
Sources/ManageMail.php

@@ -9,7 +9,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 58 - 17
Sources/ManageMaintenance.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -82,6 +82,7 @@ function ManageMaintenance()
 			'activities' => array(
 				'massmove' => 'MaintainMassMoveTopics',
 				'pruneold' => 'MaintainRemoveOldPosts',
+				'olddrafts' => 'MaintainRemoveOldDrafts',
 			),
 		),
 		'destroy' => array(
@@ -163,7 +164,7 @@ function MaintainDatabase()
 	$memory_limit = memoryReturnBytes(ini_get('memory_limit')) / (1024 * 1024);
 	// Zip limit is set to more or less 1/4th the size of the available memory * 1500
 	// 1500 is an estimate of the number of messages that generates a database of 1 MB (yeah I know IT'S AN ESTIMATION!!!)
-	// Why that? Because the only reliable zip package is the one sent out the first time, 
+	// Why that? Because the only reliable zip package is the one sent out the first time,
 	// so when the backup takes 1/5th (just to stay on the safe side) of the memory available
 	$zip_limit = $memory_limit * 1500 / 5;
 	// Here is more tricky: it depends on many factors, but the main idea is that
@@ -243,7 +244,7 @@ function MaintainMembers()
 		);
 	}
 	$smcFunc['db_free_result']($result);
-	
+
 	if (isset($_GET['done']) && $_GET['done'] == 'recountposts')
 		$context['maintenance_finished'] = $txt['maintain_recountposts'];
 }
@@ -470,8 +471,15 @@ function ConvertUtf8()
 		if ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8')
 			fatal_lang_error('utf8_already_utf8');
 
+		// Detect whether a fulltext index is set.
+		db_extend('search');
+		if ($smcFunc['db_search_support']('fulltext'))
+		{
+			require_once($sourcedir . '/ManageSearch.php');
+			detectFulltextIndex();
+		}
 		// Cannot do conversion if using a fulltext index
-		if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext')
+		if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext' || !empty($context['fulltext_index']))
 			fatal_lang_error('utf8_cannot_convert_fulltext');
 
 		// Grab the character set from the default language file.
@@ -674,9 +682,9 @@ function ConvertUtf8()
 					foreach ($columns as $column)
 					{
 						$updates_blob .= '
-							CHANGE COLUMN ' . $column['Field'] . ' ' . $column['Field'] . ' ' . strtr($column['Type'], array('text' => 'blob', 'char' => 'binary')) . ($column['Null'] === 'YES' ? ' NULL' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
+							CHANGE COLUMN `' . $column['Field'] . '` `' . $column['Field'] . '` ' . strtr($column['Type'], array('text' => 'blob', 'char' => 'binary')) . ($column['Null'] === 'YES' ? ' NULL' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
 						$updates_text .= '
-							CHANGE COLUMN ' . $column['Field'] . ' ' . $column['Field'] . ' ' . $column['Type'] . ' CHARACTER SET ' . $charsets[$_POST['src_charset']] . ($column['Null'] === 'YES' ? '' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
+							CHANGE COLUMN `' . $column['Field'] . '` `' . $column['Field'] . '` ' . $column['Type'] . ' CHARACTER SET ' . $charsets[$_POST['src_charset']] . ($column['Null'] === 'YES' ? '' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
 					}
 				}
 			}
@@ -1191,7 +1199,7 @@ function AdminBoardRecount()
 
 	isAllowedTo('admin_forum');
 	checkSession('request');
-	
+
 	// validate the request or the loop
 	if (!isset($_REQUEST['step']))
 		validateToken('admin-maint');
@@ -1807,7 +1815,7 @@ function MaintainPurgeInactiveMembers()
 			$where_vars['is_activated'] = 0;
 		}
 		else
-			$where = 'mem.last_login < {int:time_limit}';
+			$where = 'mem.last_login < {int:time_limit} AND (mem.last_login != 0 OR mem.date_registered < {int:time_limit})';
 
 		// Need to get *all* groups then work out which (if any) we avoid.
 		$request = $smcFunc['db_query']('', '
@@ -1881,6 +1889,39 @@ function MaintainRemoveOldPosts()
 	RemoveOldTopics2();
 }
 
+/**
+ * Removing old drafts
+ */
+function MaintainRemoveOldDrafts()
+{
+	global $sourcedir, $smcFunc;
+
+	validateToken('admin-maint');
+
+	$drafts = array();
+
+	// Find all of the old drafts
+	$request = $smcFunc['db_query']('', '
+		SELECT id_draft
+		FROM {db_prefix}user_drafts
+		WHERE poster_time <= {int:poster_time_old}',
+		array(
+			'poster_time_old' => time() - (86400 * $_POST['draftdays']),
+		)
+	);
+
+	while ($row = $smcFunc['db_fetch_row']($request))
+		$drafts[] = (int) $row[0];
+	$smcFunc['db_free_result']($request);
+
+	// If we have old drafts, remove them
+	if (count($drafts) > 0)
+	{
+		require_once($sourcedir . '/Drafts.php');
+		DeleteDraft($drafts, false);
+	}
+}
+
 /**
  * Moves topics from one board to another.
  *
@@ -1992,7 +2033,7 @@ function MaintainMassMoveTopics()
 /**
  * Recalculate all members post counts
  * it requires the admin_forum permission.
- * 
+ *
  * - recounts all posts for members found in the message table
  * - updates the members post count record in the members talbe
  * - honors the boards post count flag
@@ -2017,7 +2058,7 @@ function MaintainRecountPosts()
 	$context['continue_countdown'] = 3;
 	$context['continue_get_data'] = '';
 	$context['sub_template'] = 'not_done';
-	
+
 	// init
 	$increment = 200;
 	$_REQUEST['start'] = !isset($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
@@ -2029,7 +2070,7 @@ function MaintainRecountPosts()
 	if (!isset($_SESSION['total_members']))
 	{
 		validateToken('admin-maint');
-		
+
 		$request = $smcFunc['db_query']('', '
 			SELECT COUNT(DISTINCT m.id_member)
 			FROM ({db_prefix}messages AS m, {db_prefix}boards AS b)
@@ -2087,7 +2128,7 @@ function MaintainRecountPosts()
 		$_REQUEST['start'] += $increment;
 		$context['continue_get_data'] = '?action=admin;area=maintain;sa=members;activity=recountposts;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
 		$context['continue_percent'] = round(100 * $_REQUEST['start'] / $_SESSION['total_members']);
-		
+
 		createToken('admin-recountposts');
 		$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-recountposts_token_var'] . '" value="' . $context['admin-recountposts_token'] . '" />';
 
@@ -2095,7 +2136,7 @@ function MaintainRecountPosts()
 			apache_reset_timeout();
 		return;
 	}
-	
+
 	// final steps ... made more difficult since we don't yet support sub-selects on joins
 	// place all members who have posts in the message table in a temp table
 	$createTemporary = $smcFunc['db_query']('', '
@@ -2103,7 +2144,7 @@ function MaintainRecountPosts()
 			id_member mediumint(8) unsigned NOT NULL default {string:string_zero},
 			PRIMARY KEY (id_member)
 		)
-		SELECT m.id_member 
+		SELECT m.id_member
 		FROM ({db_prefix}messages AS m,{db_prefix}boards AS b)
 		WHERE m.id_member != {int:zero}
 			AND b.count_posts = {int:zero}
@@ -2117,7 +2158,7 @@ function MaintainRecountPosts()
 		)
 	) !== false;
 
-	if ($createTemporary) 
+	if ($createTemporary)
 	{
 		// outer join the members table on the temporary table finding the members that have a post count but no posts in the message table
 		$request = $smcFunc['db_query']('', '
@@ -2125,7 +2166,7 @@ function MaintainRecountPosts()
 			FROM {db_prefix}members AS mem
 			LEFT OUTER JOIN {db_prefix}tmp_maint_recountposts AS res
 			ON res.id_member = mem.id_member
-			WHERE res.id_member IS null 
+			WHERE res.id_member IS null
 				AND mem.posts != {int:zero}',
 			array(
 				'zero' => 0,
@@ -2147,7 +2188,7 @@ function MaintainRecountPosts()
 		}
 		$smcFunc['db_free_result']($request);
 	}
-	
+
 	// all done
 	unset($_SESSION['total_members']);
 	$context['maintenance_finished'] = $txt['maintain_recountposts'];

+ 1 - 5
Sources/ManageMembergroups.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -196,8 +196,6 @@ function MembergroupIndex()
 		),
 	);
 
-	call_integration_hook('integrate_modify_regular_groups', array(&$listOptions));
-
 	require_once($sourcedir . '/Subs-List.php');
 	createList($listOptions);
 
@@ -309,8 +307,6 @@ function MembergroupIndex()
 		),
 	);
 
-	call_integration_hook('integrate_modify_post_groups', array(&$listOptions));
-
 	createList($listOptions);
 }
 

+ 16 - 18
Sources/ManageMembers.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -369,7 +369,10 @@ function ViewMemberlist()
 				// Replace the wildcard characters ('*' and '?') into MySQL ones.
 				$parameter = strtolower(strtr($smcFunc['htmlspecialchars']($search_params[$param_name], ENT_QUOTES), array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_')));
 
-				$query_parts[] = '(' . implode( ' LIKE {string:' . $param_name . '_normal} OR ', $param_info['db_fields']) . ' LIKE {string:' . $param_name . '_normal})';
+				if ($smcFunc['db_case_sensitive'])
+					$query_parts[] = '(LOWER(' . implode( ') LIKE {string:' . $param_name . '_normal} OR LOWER(', $param_info['db_fields']) . ') LIKE {string:' . $param_name . '_normal})';
+				else
+					$query_parts[] = '(' . implode( ' LIKE {string:' . $param_name . '_normal} OR ', $param_info['db_fields']) . ' LIKE {string:' . $param_name . '_normal})';
 				$where_params[$param_name . '_normal'] = '%' . $parameter . '%';
 			}
 		}
@@ -602,12 +605,10 @@ function ViewMemberlist()
 		),
 	);
 
-	// Without not enough permissions, don't show 'delete members' checkboxes.
+	// Without enough permissions, don't show 'delete members' checkboxes.
 	if (!allowedTo('profile_remove_any'))
 		unset($listOptions['cols']['check'], $listOptions['form'], $listOptions['additional_rows']);
 
-	call_integration_hook('integrate_view_members_list', array(&$listOptions));
-
 	require_once($sourcedir . '/Subs-List.php');
 	createList($listOptions);
 
@@ -963,15 +964,13 @@ function MembersAwaitingActivation()
 			array(
 				'position' => 'below_table_data',
 				'value' => '
-					<div class="floatleft">
-						[<a href="' . $scripturl . '?action=admin;area=viewmembers;sa=browse;showdupes=' . ($context['show_duplicates'] ? 0 : 1) . ';type=' . $context['browse_type'] . (!empty($context['show_filter']) ? ';filter=' . $context['current_filter'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'] . '">' . ($context['show_duplicates'] ? $txt['dont_check_for_duplicate'] : $txt['check_for_duplicate']) . '</a>]
-					</div>
-					<div class="floatright">
-						<select name="todo" onchange="onSelectChange();">
-							' . $allowed_actions . '
-						</select>
-						<noscript><input type="submit" value="' . $txt['go'] . '" class="button_submit" /></noscript>
-					</div>',
+					[<a href="' . $scripturl . '?action=admin;area=viewmembers;sa=browse;showdupes=' . ($context['show_duplicates'] ? 0 : 1) . ';type=' . $context['browse_type'] . (!empty($context['show_filter']) ? ';filter=' . $context['current_filter'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'] . '">' . ($context['show_duplicates'] ? $txt['dont_check_for_duplicate'] : $txt['check_for_duplicate']) . '</a>]
+					<select name="todo" onchange="onSelectChange();">
+						' . $allowed_actions . '
+					</select>
+					<noscript><input type="submit" value="' . $txt['go'] . '" class="button_submit" /><br class="clear_right"></noscript>
+				',
+				'class' => 'floatright',
 			),
 		),
 	);
@@ -999,9 +998,9 @@ function MembersAwaitingActivation()
 			</select>
 			<noscript><input type="submit" value="' . $txt['go'] . '" name="filter" class="button_submit" /></noscript>';
 		$listOptions['additional_rows'][] = array(
-			'position' => 'above_column_headers',
+			'position' => 'top_of_list',
 			'value' => $filterOptions,
-			'style' => 'text-align: center;',
+			'class' => 'righttext',
 		);
 	}
 
@@ -1010,8 +1009,7 @@ function MembersAwaitingActivation()
 		$listOptions['additional_rows'][] = array(
 			'position' => 'above_column_headers',
 			'value' => '<strong>' . $txt['admin_browse_filter_show'] . ':</strong> ' . $context['available_filters'][0]['desc'],
-			'class' => 'smalltext',
-			'style' => 'text-align: left;',
+			'class' => 'smalltext floatright',
 		);
 
 	// Now that we have all the options, create the list.

+ 5 - 4
Sources/ManageNews.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -151,7 +151,7 @@ function EditNews()
 					'function' => create_function('$news', '
 
 						if (is_numeric($news[\'id\']))
-							return \'<textarea id="data_\' . $news[\'id\'] . \'" rows="3" cols="65" name="news[]" style="\' . (isBrowser(\'is_ie8\') ? \'width: 635px; max-width: 85%; min-width: 85%\' : \'width 100%;margin 0 5em\') . \';">\' . $news[\'unparsed\'] . \'</textarea>
+							return \'<textarea id="data_\' . $news[\'id\'] . \'" rows="3" cols="50" name="news[]" style="\' . (isBrowser(\'is_ie8\') ? \'width: 635px; max-width: 85%; min-width: 85%\' : \'width 100%;margin 0 5em\') . \';">\' . $news[\'unparsed\'] . \'</textarea>
 							<br />
 							<div class="floatleft" id="preview_\' . $news[\'id\'] . \'"></div>\';
 						else
@@ -201,7 +201,7 @@ function EditNews()
 				<span id="moreNewsItems_link" class="floatleft" style="display: none;">
 					<a class="button_link" href="javascript:void(0);" onclick="addNewsItem(); return false;">' . $txt['editnews_clickadd'] . '</a>
 				</span>
-				<input type="submit" name="save_items" value="' . $txt['save'] . '" class="button_submit" /> 
+				<input type="submit" name="save_items" value="' . $txt['save'] . '" class="button_submit" />
 				<input type="submit" name="delete_selection" value="' . $txt['editnews_remove_selected'] . '" onclick="return confirm(\'' . $txt['editnews_remove_confirm'] . '\');" class="button_submit" />',
 			),
 		),
@@ -209,7 +209,7 @@ function EditNews()
 					document.getElementById(\'list_news_lists_last\').style.display = "none";
 					document.getElementById("moreNewsItems_link").style.display = "";
 					var last_preview = 0;
-					
+
 					$(document).ready(function () {
 						$("div[id ^= \'preview_\']").each(function () {
 							var preview_id = $(this).attr(\'id\').split(\'_\')[1];
@@ -688,6 +688,7 @@ function SendMailing($clean_only = false)
 	}
 
 	// How many to send at once? Quantity depends on whether we are queueing or not.
+	// @todo Might need an interface? (used in Post.php too with different limits)
 	$num_at_once = empty($modSettings['mail_queue']) ? 60 : 1000;
 
 	// If by PM's I suggest we half the above number.

+ 6 - 9
Sources/ManagePaid.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -686,7 +686,7 @@ function ViewSubscribedUsers()
 			'payments_pending' => array(
 				'header' => array(
 					'value' => $txt['paid_payments_pending'],
-					'style' => 'text-align: left; width: 10%;',
+					'style' => 'text-align: left; width: 15%;',
 				),
 				'data' => array(
 					'db_htmlsafe' => 'pending',
@@ -758,9 +758,7 @@ function ViewSubscribedUsers()
 			array(
 				'position' => 'below_table_data',
 				'value' => '
-					<div class="floatleft">
-						<input type="submit" name="add" value="' . $txt['add_subscriber'] . '" class="button_submit" />
-					</div>
+					<input type="submit" name="add" value="' . $txt['add_subscriber'] . '" class="button_submit" />
 					<input type="submit" name="finished" value="' . $txt['complete_selected'] . '" onclick="return confirm(\'' . $txt['complete_are_sure'] . '\');" class="button_submit" />
 					<input type="submit" name="delete" value="' . $txt['delete_selected'] . '" onclick="return confirm(\'' . $txt['delete_are_sure'] . '\');" class="button_submit" />
 				',
@@ -768,10 +766,9 @@ function ViewSubscribedUsers()
 			array(
 				'position' => 'top_of_list',
 				'value' => '
-
-					<div class="floatright">
-						<input type="text" name="sub_search" value="" class="input_text" />
-						<input type="submit" name="ssearch" value="' . $txt['search_sub'] . '" class="button_submit" />
+					<div class="flow_auto">
+						<input type="submit" name="ssearch" value="' . $txt['search_sub'] . '" class="button_submit" style="margin-top: 3px;" />
+						<input type="text" name="sub_search" value="" class="input_text floatright" />
 					</div>
 				',
 			),

+ 7 - 1
Sources/ManagePermissions.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -1463,6 +1463,8 @@ function loadAllPermissions($loadType = 'classic')
 			'disable_censor' => array(false, 'general', 'disable_censor'),
 			'pm_read' => array(false, 'pm', 'use_pm_system'),
 			'pm_send' => array(false, 'pm', 'use_pm_system'),
+			'pm_draft' => array(false, 'pm', 'use_pm_system'),
+			'pm_autosave_draft' => array(false, 'pm', 'use_pm_system'),
 			'send_email_to_members' => array(false, 'pm', 'use_pm_system'),
 			'calendar_view' => array(false, 'calendar', 'view_basic_info'),
 			'calendar_post' => array(false, 'calendar', 'post_calendar'),
@@ -1492,6 +1494,8 @@ function loadAllPermissions($loadType = 'classic')
 			'moderate_board' => array(false, 'general_board', 'moderate'),
 			'approve_posts' => array(false, 'general_board', 'moderate'),
 			'post_new' => array(false, 'topic', 'make_posts'),
+			'post_draft' => array(false, 'topic', 'make_posts'),
+			'post_autosave_draft' => array(false, 'topic', 'make_posts'),
 			'post_unapproved_topics' => array(false, 'topic', 'make_unapproved_posts'),
 			'post_unapproved_replies' => array(true, 'topic', 'make_unapproved_posts', 'make_unapproved_posts'),
 			'post_reply' => array(true, 'topic', 'make_posts', 'make_posts'),
@@ -2249,6 +2253,8 @@ function loadIllegalGuestPermissions()
 		'modify_replies',
 		'send_mail',
 		'approve_posts',
+		'post_draft',
+		'post_autosave_draft',
 	);
 
 	call_integration_hook('integrate_load_illegal_guest_permissions');

+ 7 - 3
Sources/ManagePosts.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -45,7 +45,7 @@ function ManagePostSettings()
 
 	$context['page_title'] = $txt['manageposts_title'];
 
-	// Tabs for browsing the different ban functions.
+	// Tabs for browsing the different post functions.
 	$context[$context['admin_menu_name']]['tab_data'] = array(
 		'title' => $txt['manageposts_title'],
 		'help' => 'posts_and_topics',
@@ -232,7 +232,7 @@ function ModifyPostSettings($return_config = false)
 				fatal_lang_error('convert_to_mediumtext', false, array($scripturl . '?action=admin;area=maintain;sa=database'));
 
 		}
-		
+
 		// If we're changing the post preview length let's check its valid
 		if (!empty($_POST['preview_characters']))
 			$_POST['preview_characters'] = (int) min(max(0, $_POST['preview_characters']), 512);
@@ -266,12 +266,16 @@ function ModifyBBCSettings($return_config = false)
 	$config_vars = array(
 			// Main tweaks
 			array('check', 'enableBBC'),
+			array('check', 'enableBBC', 0, 'onchange' => 'toggleBBCDisabled(\'disabledBBC\', !this.checked);'),
 			array('check', 'enablePostHTML'),
 			array('check', 'autoLinkUrls'),
 		'',
 			array('bbc', 'disabledBBC'),
 	);
 
+	$context['settings_post_javascript'] = '
+		toggleBBCDisabled(\'disabledBBC\', ' . (empty($modSettings['enableBBC']) ? 'true' : 'false') . ');';
+
 	call_integration_hook('integrate_modify_bbc_settings', array(&$config_vars));
 
 	if ($return_config)

+ 1 - 1
Sources/ManageRegistration.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/ManageScheduledTasks.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 54 - 49
Sources/ManageSearch.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -216,52 +216,7 @@ function EditSearchMethod()
 
 	// Detect whether a fulltext index is set.
 	if ($context['supports_fulltext'])
-	{
-		$request = $smcFunc['db_query']('', '
-			SHOW INDEX
-			FROM {db_prefix}messages',
-			array(
-			)
-		);
-		$context['fulltext_index'] = '';
-		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
-		{
-			while ($row = $smcFunc['db_fetch_assoc']($request))
-				if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
-					$context['fulltext_index'][] = $row['Key_name'];
-			$smcFunc['db_free_result']($request);
-
-			if (is_array($context['fulltext_index']))
-				$context['fulltext_index'] = array_unique($context['fulltext_index']);
-		}
-
-		if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
-			$request = $smcFunc['db_query']('', '
-				SHOW TABLE STATUS
-				FROM {string:database_name}
-				LIKE {string:table_name}',
-				array(
-					'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
-					'table_name' => str_replace('_', '\_', $match[2]) . 'messages',
-				)
-			);
-		else
-			$request = $smcFunc['db_query']('', '
-				SHOW TABLE STATUS
-				LIKE {string:table_name}',
-				array(
-					'table_name' => str_replace('_', '\_', $db_prefix) . 'messages',
-				)
-			);
-
-		if ($request !== false)
-		{
-			while ($row = $smcFunc['db_fetch_assoc']($request))
-				if ((isset($row['Type']) && strtolower($row['Type']) != 'myisam') || (isset($row['Engine']) && strtolower($row['Engine']) != 'myisam'))
-					$context['cannot_create_fulltext'] = true;
-			$smcFunc['db_free_result']($request);
-		}
-	}
+		detectFulltextIndex();
 
 	if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'createfulltext')
 	{
@@ -542,7 +497,7 @@ function CreateMessageIndex()
 		);
 		$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
 		$context['step'] = isset($_REQUEST['step']) ? (int) $_REQUEST['step'] : 0;
-		
+
 		// admin timeouts are painful when building these long indexes
 		if ($_SESSION['admin_time'] + 3300 < time() && $context['step'] >= 1)
 			$_SESSION['admin_time'] = time();
@@ -722,7 +677,7 @@ function CreateMessageIndex()
 			$context['percentage'] = 80 + round($context['start'] / $index_properties[$context['index_settings']['bytes_per_word']]['max_size'], 3) * 20;
 		}
 	}
-	
+
 	// Step 3: remove words not distinctive enough.
 	if ($context['step'] === 3)
 	{
@@ -789,3 +744,53 @@ function loadSearchAPIs()
 
 	return $apis;
 }
+
+function detectFulltextIndex()
+{
+	global $smcFunc, $context, $db_prefix;
+
+	$request = $smcFunc['db_query']('', '
+		SHOW INDEX
+		FROM {db_prefix}messages',
+		array(
+		)
+	);
+	$context['fulltext_index'] = '';
+	if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
+	{
+		while ($row = $smcFunc['db_fetch_assoc']($request))
+			if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
+				$context['fulltext_index'][] = $row['Key_name'];
+		$smcFunc['db_free_result']($request);
+
+		if (is_array($context['fulltext_index']))
+			$context['fulltext_index'] = array_unique($context['fulltext_index']);
+	}
+
+	if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
+		$request = $smcFunc['db_query']('', '
+			SHOW TABLE STATUS
+			FROM {string:database_name}
+			LIKE {string:table_name}',
+			array(
+				'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
+				'table_name' => str_replace('_', '\_', $match[2]) . 'messages',
+			)
+		);
+	else
+		$request = $smcFunc['db_query']('', '
+			SHOW TABLE STATUS
+			LIKE {string:table_name}',
+			array(
+				'table_name' => str_replace('_', '\_', $db_prefix) . 'messages',
+			)
+		);
+
+	if ($request !== false)
+	{
+		while ($row = $smcFunc['db_fetch_assoc']($request))
+			if ((isset($row['Type']) && strtolower($row['Type']) != 'myisam') || (isset($row['Engine']) && strtolower($row['Engine']) != 'myisam'))
+				$context['cannot_create_fulltext'] = true;
+		$smcFunc['db_free_result']($request);
+	}
+}

+ 4 - 4
Sources/ManageSearchEngines.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -459,7 +459,7 @@ function EditSpider()
 
 /**
  * Do we think the current user is a spider?
- * 
+ *
  * @todo Should this not be... you know... in a different file?
  * @return int
  */
@@ -538,7 +538,7 @@ function SpiderCheck()
 
 /**
  * Log the spider presence online.
- * 
+ *
  * @todo Different file?
  */
 function logSpider()
@@ -798,7 +798,7 @@ function SpiderLogs()
 
 /**
  * Callback function for createList()
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort

+ 20 - 39
Sources/ManageServer.php

@@ -1,7 +1,7 @@
 <?php
 
 /**
- * Contains all the functionality required to be able to edit the core server 
+ * Contains all the functionality required to be able to edit the core server
  * settings. This includes anything from which an error may result in the forum
  * destroying itself in a firey fury.
  *
@@ -50,7 +50,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -104,7 +104,7 @@ function ModifySettings()
 	// By default we're editing the core settings
 	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'general';
 	$context['sub_action'] = $_REQUEST['sa'];
-	
+
 	// Any messages to speak of?
 	$context['settings_message'] = (isset($_REQUEST['msg']) && isset($txt[$_REQUEST['msg']])) ? $txt[$_REQUEST['msg']] : '';
 
@@ -323,7 +323,7 @@ function ModifyCookieSettings($return_config = false)
 function ModifyCacheSettings($return_config = false)
 {
 	global $context, $scripturl, $txt, $helptxt, $cache_enable;
-	
+
 	// Detect all available optimizers
 	$detected = array();
 	if (function_exists('eaccelerator_put'))
@@ -338,13 +338,13 @@ function ModifyCacheSettings($return_config = false)
 		$detected['memcached'] = $txt['memcached_cache'];
 	if (function_exists('xcache_set'))
 		$detected['xcache'] = $txt['xcache_cache'];
-		
+
 	// set a message to show what, if anything, we found
 	if (empty($detected))
 		$txt['cache_settings_message'] = $txt['detected_no_caching'];
 	else
 		$txt['cache_settings_message'] = sprintf($txt['detected_accelerators'], implode(', ', $detected));
-	
+
 	// This is always an option
 	$detected['smf'] = $txt['default_cache'];
 
@@ -357,32 +357,13 @@ function ModifyCacheSettings($return_config = false)
 		array('cache_memcached', $txt['cache_memcached'], 'file', 'text', $txt['cache_memcached'], 'cache_memcached'),
 		array('cachedir', $txt['cachedir'], 'file', 'text', 36, 'cache_cachedir'),
 	);
-	
+
 	// 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));
 
@@ -395,14 +376,14 @@ function ModifyCacheSettings($return_config = false)
 		call_integration_hook('integrate_save_cache_settings');
 
 		saveSettings($config_vars);
-		
+
 		// we need to save the $cache_enable to $modSettings as well
 		updatesettings(array('cache_enable' => (int) $_POST['cache_enable']));
 
 		// exit so we reload our new settings on the page
 		redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']);
 	}
-	
+
 	// if its off, allow them to clear it as well
 	// @todo why only when its off ?
 	if (empty($cache_enable))
@@ -507,7 +488,7 @@ function ModifyLoadBalancingSettings($return_config = false)
 		saveDBSettings($config_vars);
 		redirectexit('action=admin;area=serversettings;sa=loads;' . $context['session_var'] . '=' . $context['session_id']);
 	}
-	
+
 	createToken('admin-ssc');
 	createToken('admin-dbsc');
 	prepareDBSettingContext($config_vars);
@@ -570,7 +551,7 @@ function prepareServerSettingsContext(&$config_vars)
 				'preinput' => !empty($config_var['preinput']) ? $config_var['preinput'] : '',
 				'postinput' => !empty($config_var['postinput']) ? $config_var['postinput'] : '',
 			);
-			
+
 			// If this is a select box handle any data.
 			if (!empty($config_var[4]) && is_array($config_var[4]))
 			{
@@ -794,15 +775,15 @@ function saveSettings(&$config_vars)
 		'cookiename',
 		'webmaster_email',
 		'db_name', 'db_user', 'db_server', 'db_prefix', 'ssi_db_user',
-		'boarddir', 'sourcedir', 
+		'boarddir', 'sourcedir',
 		'cachedir', 'cache_accelerator', 'cache_memcached',
 	);
-	
+
 	// All the numeric variables.
 	$config_ints = array(
 		'cache_enable',
 	);
-	
+
 	// All the checkboxes.
 	$config_bools = array(
 		'db_persist', 'db_error_send',
@@ -944,7 +925,7 @@ function saveDBSettings(&$config_vars)
 function ShowPHPinfoSettings()
 {
 	global $context, $txt;
-	
+
 	$info_lines = array();
 	$category = $txt['phpinfo_settings'];
 
@@ -956,8 +937,8 @@ 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 sensative
+
+	// remove things that could be considered sensitive
 	$remove = '_COOKIE|Cookie|_GET|_REQUEST|REQUEST_URI|QUERY_STRING|REQUEST_URL|HTTP_REFERER';
 
 	// put all of it into an array

+ 137 - 68
Sources/ManageSettings.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -225,6 +225,38 @@ function ModifyCoreFeatures($return_config = false)
 					return array();
 			'),
 		),
+		// dr = drafts
+		'dr' => array(
+			'url' => 'action=admin;area=managedrafts',
+			'settings' => array(
+				'drafts_enabled' => 1,
+				'drafts_post_enabled' => 2,
+				'drafts_pm_enabled' => 2,
+				'drafts_autosave_enabled' => 2,
+				'drafts_show_saved_enabled' => 2,
+			),
+			'setting_callback' => create_function('$value', '
+				global $smcFunc, $sourcedir;
+
+				// Set the correct disabled value for the scheduled task.
+				$smcFunc[\'db_query\'](\'\', \'
+					UPDATE {db_prefix}scheduled_tasks
+					SET disabled = {int:disabled}
+					WHERE task = {string:task}\',
+					array(
+						\'disabled\' => $value ? 0 : 1,
+						\'task\' => \'remove_old_drafts\',
+					)
+				);
+			'),
+		),
+		// ih = Integration Hooks Handling.
+		'ih' => array(
+			'url' => 'action=admin;area=modsettings;sa=hooks',
+			'settings' => array(
+				'handlinghooks_enabled' => 1,
+			),
+		),
 		// k = karma.
 		'k' => array(
 			'url' => 'action=admin;area=featuresettings;sa=karma',
@@ -357,15 +389,15 @@ function ModifyCoreFeatures($return_config = false)
 	{
 		checkSession();
 
-	if (isset($_GET['xml']))
-	{
-		$tokenValidation = validateToken('admin-core', 'post', false);
+		if (isset($_GET['xml']))
+		{
+			$tokenValidation = validateToken('admin-core', 'post', false);
 
-		if (empty($tokenValidation))
-			return 'token_verify_fail';
-	}
-	else
-		validateToken('admin-core');
+			if (empty($tokenValidation))
+				return 'token_verify_fail';
+		}
+		else
+			validateToken('admin-core');
 
 		$setting_changes = array('admin_features' => array());
 
@@ -478,7 +510,6 @@ function ModifyBasicSettings($return_config = false)
 		'',
 			// Number formatting, timezones.
 			array('text', 'time_format'),
-			array('select', 'number_format', array('1234.00' => '1234.00', '1,234.00' => '1,234.00', '1.234,00' => '1.234,00', '1 234,00' => '1 234,00', '1234,00' => '1234,00')),
 			array('float', 'time_offset', 'subtext' => $txt['setting_time_offset_note'], 6, 'postinput' => $txt['hours']),
 			'default_timezone' => array('select', 'default_timezone', array()),
 		'',
@@ -492,7 +523,7 @@ function ModifyBasicSettings($return_config = false)
 		'',
 			// Option-ish things... miscellaneous sorta.
 			array('check', 'allow_disableAnnounce'),
-			array('check', 'disallow_sendBody'),	
+			array('check', 'disallow_sendBody'),
 	);
 
 	// Get all the time zones.
@@ -562,6 +593,7 @@ function ModifyGeneralSecuritySettings($return_config = false)
 		'',
 			// Password strength.
 			array('select', 'password_strength', array($txt['setting_password_strength_low'], $txt['setting_password_strength_medium'], $txt['setting_password_strength_high'])),
+			array('check', 'enable_password_conversion'),
 		'',
 			// Reporting of personal messages?
 			array('check', 'enableReportPM'),
@@ -612,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'),
@@ -1737,7 +1769,7 @@ function EditCustomProfiles()
 		// Regex you say?  Do a very basic test to see if the pattern is valid
 		if (!empty($_POST['regex']) && @preg_match($_POST['regex'], 'dummy') === false)
 			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error');
-			
+
 		$_POST['field_name'] = $smcFunc['htmlspecialchars']($_POST['field_name']);
 		$_POST['field_desc'] = $smcFunc['htmlspecialchars']($_POST['field_desc']);
 
@@ -2168,34 +2200,41 @@ function list_integration_hooks()
 	global $sourcedir, $scripturl, $context, $txt, $modSettings, $settings;
 
 	$context['filter'] = '';
-	$presentHooks = get_integration_hooks();
-	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($presentHooks)))
+	$currentHooks = get_integration_hooks();
+	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($currentHooks)))
 		$context['filter'] = ';filter=' . $_GET['filter'];
 
-	if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
+	if (!empty($modSettings['handlinghooks_enabled']))
 	{
-		checkSession('request');
-		validateToken('admin-hook', 'request');
-
-		if ($_REQUEST['do'] == 'remove')
-			remove_integration_function($_REQUEST['hook'], $_REQUEST['function']);
-		elseif ($_REQUEST['do'] == 'disable')
-		{
-			remove_integration_function($_REQUEST['hook'], $_REQUEST['function']);
-			// It's a hack I know...but I'm way too lazy!!!
-			add_integration_function($_REQUEST['hook'], $_REQUEST['function'] . ']');
-		}
-		elseif ($_REQUEST['do'] == 'enable')
+		if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
 		{
-			remove_integration_function($_REQUEST['hook'], $_REQUEST['function'] . ']');
-			// It's a hack I know...but I'm way too lazy!!!
-			add_integration_function($_REQUEST['hook'], $_REQUEST['function']);
-		}
+			checkSession('request');
+			validateToken('admin-hook', 'request');
 
-		redirectexit('action=admin;area=modsettings;sa=hooks' . $context['filter']);
-	}
+			if ($_REQUEST['do'] == 'remove')
+				remove_integration_function($_REQUEST['hook'], urldecode($_REQUEST['function']));
+			else
+			{
+				if ($_REQUEST['do'] == 'disable')
+				{
+					// It's a hack I know...but I'm way too lazy!!!
+					$function_remove = $_REQUEST['function'];
+					$function_add = $_REQUEST['function'] . ']';
+				}
+				else
+				{
+					$function_remove = $_REQUEST['function'] . ']';
+					$function_add = $_REQUEST['function'];
+				}
+				$file = !empty($_REQUEST['includedfile']) ? urldecode($_REQUEST['includedfile']) : '';
 
-	createToken('admin-hook', 'request');
+				remove_integration_function($_REQUEST['hook'], $function_remove, $file);
+				add_integration_function($_REQUEST['hook'], $function_add, $file);
+
+				redirectexit('action=admin;area=modsettings;sa=hooks' . $context['filter']);
+			}
+		}
+	}
 
 	$list_options = array(
 		'id' => 'list_integration_hooks',
@@ -2228,7 +2267,14 @@ function list_integration_hooks()
 					'value' => $txt['hooks_field_function_name'],
 				),
 				'data' => array(
-					'db' => 'function_name',
+					'function' => create_function('$data', '
+						global $txt;
+
+						if (!empty($data[\'included_file\']))
+							return $txt[\'hooks_field_function\'] . \': \' . $data[\'real_function\'] . \'<br />\' . $txt[\'hooks_field_included_file\'] . \': \' . $data[\'included_file\'];
+						else
+							return $data[\'real_function\'];
+					'),
 				),
 				'sort' =>  array(
 					'default' => 'function_name',
@@ -2259,7 +2305,7 @@ function list_integration_hooks()
 						$change_status = array(\'before\' => \'\', \'after\' => \'\');
 						if ($data[\'can_be_disabled\'] && $data[\'status\'] != \'deny\')
 						{
-							$change_status[\'before\'] = \'<a href="\' . $scripturl . \'?action=admin;area=modsettings;sa=hooks;do=\' . ($data[\'enabled\'] ? \'disable\' : \'enable\') . \';hook=\' . $data[\'hook_name\'] . \';function=\' . $data[\'function_name\'] . $context[\'filter\'] . \';\' . $context[\'admin-hook_token_var\'] . \'=\' . $context[\'admin-hook_token\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'" onclick="return confirm(\' . javaScriptEscape($txt[\'quickmod_confirm\']) . \');">\';
+							$change_status[\'before\'] = \'<a href="\' . $scripturl . \'?action=admin;area=modsettings;sa=hooks;do=\' . ($data[\'enabled\'] ? \'disable\' : \'enable\') . \';hook=\' . $data[\'hook_name\'] . \';function=\' . $data[\'real_function\'] . (!empty($data[\'included_file\']) ? \';includedfile=\' . urlencode($data[\'included_file\']) : \'\') . $context[\'filter\'] . \';\' . $context[\'admin-hook_token_var\'] . \'=\' . $context[\'admin-hook_token\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'" onclick="return confirm(\' . javaScriptEscape($txt[\'quickmod_confirm\']) . \');">\';
 							$change_status[\'after\'] = \'</a>\';
 						}
 						return $change_status[\'before\'] . \'<img src="\' . $settings[\'images_url\'] . \'/admin/post_moderation_\' . $data[\'status\'] . \'.png" alt="\' . $data[\'img_text\'] . \'" title="\' . $data[\'img_text\'] . \'" />\' . $change_status[\'after\'];
@@ -2271,28 +2317,6 @@ function list_integration_hooks()
 					'reverse' => 'status DESC',
 				),
 			),
-			'check' => array(
-				'header' => array(
-					'value' => $txt['hooks_button_remove'],
-					'style' => 'width:3%',
-				),
-				'data' => array(
-					'function' => create_function('$data', '
-						global $txt, $settings, $scripturl, $context;
-
-						if (!$data[\'hook_exists\'])
-							return \'
-							<a href="\' . $scripturl . \'?action=admin;area=modsettings;sa=hooks;do=remove;hook=\' . $data[\'hook_name\'] . \';function=\' . $data[\'function_name\'] . $context[\'filter\'] . \';\' . $context[\'admin-hook_token_var\'] . \'=\' . $context[\'admin-hook_token\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'" onclick="return confirm(\' . javaScriptEscape($txt[\'quickmod_confirm\']) . \');">
-								<img src="\' . $settings[\'images_url\'] . \'/icons/quick_remove.png" alt="\' . $txt[\'hooks_button_remove\'] . \'" title="\' . $txt[\'hooks_button_remove\'] . \'" />
-							</a>\';
-					'),
-					'class' => 'centertext',
-				),
-			),
-		),
-		'form' => array(
-			'href' => $scripturl . '?action=admin;area=modsettings;sa=hooks' . $context['filter'] . ';' . $context['session_var'] . '=' . $context['session_id'],
-			'name' => 'list_integration_hooks',
 		),
 		'additional_rows' => array(
 			array(
@@ -2308,6 +2332,35 @@ function list_integration_hooks()
 		),
 	);
 
+	if (!empty($modSettings['handlinghooks_enabled']))
+	{
+		createToken('admin-hook', 'request');
+
+		$list_options['columns']['remove'] = array(
+			'header' => array(
+				'value' => $txt['hooks_button_remove'],
+				'style' => 'width:3%',
+			),
+			'data' => array(
+				'function' => create_function('$data', '
+					global $txt, $settings, $scripturl, $context;
+
+					if (!$data[\'hook_exists\'])
+						return \'
+						<a href="\' . $scripturl . \'?action=admin;area=modsettings;sa=hooks;do=remove;hook=\' . $data[\'hook_name\'] . \';function=\' . urlencode($data[\'function_name\']) . $context[\'filter\'] . \';\' . $context[\'admin-hook_token_var\'] . \'=\' . $context[\'admin-hook_token\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'" onclick="return confirm(\' . javaScriptEscape($txt[\'quickmod_confirm\']) . \');">
+							<img src="\' . $settings[\'images_url\'] . \'/icons/quick_remove.png" alt="\' . $txt[\'hooks_button_remove\'] . \'" title="\' . $txt[\'hooks_button_remove\'] . \'" />
+						</a>\';
+				'),
+				'class' => 'centertext',
+			),
+		);
+		$list_options['form'] = array(
+			'href' => $scripturl . '?action=admin;area=modsettings;sa=hooks' . $context['filter'] . ';' . $context['session_var'] . '=' . $context['session_id'],
+			'name' => 'list_integration_hooks',
+		);
+	}
+
+
 	require_once($sourcedir . '/Subs-List.php');
 	createList($list_options);
 
@@ -2340,7 +2393,7 @@ function get_files_recursive($dir_path)
 
 function get_integration_hooks_data($start, $per_page, $sort)
 {
-	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl;
+	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl, $modSettings;
 
 	$hooks = $temp_hooks = get_integration_hooks();
 	$hooks_data = $temp_data = $hook_status = array();
@@ -2360,7 +2413,15 @@ function get_integration_hooks_data($start, $per_page, $sort)
 				{
 					foreach ($functions as $function_o)
 					{
-						$function = str_replace(']', '', $function_o);
+						$hook_name = str_replace(']', '', $function_o);
+						if (strpos($hook_name, '::') !== false)
+						{
+							$function = explode('::', $hook_name);
+							$function = $function[1];
+						}
+						$function = explode(':', $function);
+						$function = $function[0];
+
 						if (substr($hook, -8) === '_include')
 						{
 							$hook_status[$hook][$function]['exists'] = file_exists(strtr(trim($function), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
@@ -2368,11 +2429,10 @@ function get_integration_hooks_data($start, $per_page, $sort)
 							$temp_data['include'][basename($function)] = array('hook' => $hook, 'function' => $function);
 							unset($temp_hooks[$hook][$function_o]);
 						}
-						// @TODO replace with a preg_match? (the difference is the space before the open parentheses
-						elseif (strpos($fc, 'function ' . trim($function) . '(') !== false || strpos($fc, 'function ' . trim($function) . ' (') !== false)
+						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($function) . '(') !== false)
 						{
-							$hook_status[$hook][$function]['exists'] = true;
-							$hook_status[$hook][$function]['in_file'] = $file['name'];
+							$hook_status[$hook][$hook_name]['exists'] = true;
+							$hook_status[$hook][$hook_name]['in_file'] = $file['name'];
 							// I want to remember all the functions called within this file (to check later if they are enabled or disabled and decide if the integrare_*_include of that file can be disabled too)
 							$temp_data['function'][$file['name']][] = $function_o;
 							unset($temp_hooks[$hook][$function_o]);
@@ -2440,18 +2500,27 @@ function get_integration_hooks_data($start, $per_page, $sort)
 				$function = str_replace(']', '', $function);
 				$hook_exists = !empty($hook_status[$hook][$function]['exists']);
 				$file_name = isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ((substr($hook, -8) === '_include') ? 'zzzzzzzzz' : 'zzzzzzzza');
-				$status = $hook_exists ? ($enabled ? 'a' : 'b') : 'c';
 				$sort[] = $$sort_options[0];
+
+				if (strpos($function, '::') !== false)
+				{
+					$function = explode('::', $function);
+					$function = $function[1];
+				}
+				$exploded = explode(':', $function);
+
 				$temp_data[] = array(
 					'id' => 'hookid_' . $id++,
 					'hook_name' => $hook,
 					'function_name' => $function,
+					'real_function' => $exploded[0],
+					'included_file' => isset($exploded[1]) ? strtr(trim($exploded[1]), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])) : '',
 					'file_name' => (isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ''),
 					'hook_exists' => $hook_exists,
 					'status' => $hook_exists ? ($enabled ? 'allow' : 'moderate') : 'deny',
 					'img_text' => $txt['hooks_' . ($hook_exists ? ($enabled ? 'active' : 'disabled') : 'missing')],
 					'enabled' => $enabled,
-					'can_be_disabled' => !isset($hook_status[$hook][$function]['enabled']),
+					'can_be_disabled' => !empty($modSettings['handlinghooks_enabled']) && !isset($hook_status[$hook][$function]['enabled']),
 				);
 			}
 		}

+ 2 - 2
Sources/ManageSmileys.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -207,7 +207,7 @@ function EditSmileySets()
 			$set_paths = explode(',', $modSettings['smiley_sets_known']);
 			$set_names = explode("\n", $modSettings['smiley_sets_names']);
 			foreach ($_POST['smiley_set'] as $id => $val)
-			{	
+			{
 				if (isset($set_paths[$id], $set_names[$id]) && !empty($id))
 					unset($set_paths[$id], $set_names[$id]);
 			}

+ 23 - 18
Sources/Memberlist.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -150,7 +150,7 @@ function Memberlist()
 			),
 		)
 	);
-	
+
 	$context['colspan'] = 0;
 	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
 	foreach ($context['columns'] as $key => $column)
@@ -175,15 +175,15 @@ function Memberlist()
 
 	$context['can_send_pm'] = allowedTo('pm_send');
 	$context['can_send_email'] = allowedTo('send_email_to_members');
-	
+
 	// Build the memberlist button array.
 	$context['memberlist_buttons'] = array(
 		'view_all_members' => array('text' => 'view_all_members', 'image' => 'mlist.png', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=all', 'active'=> true),
 		'mlist_search' => array('text' => 'mlist_search', 'image' => 'mlist.png', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=search'),
 	);
-	
+
 	// Allow mods to add additional buttons here
-	call_integration_hook('integrate_memberlist_buttons');	
+	call_integration_hook('integrate_memberlist_buttons');
 
 	// Jump to the sub action.
 	if (isset($subActions[$context['listing_by']]))
@@ -406,6 +406,9 @@ function MLSearch()
 {
 	global $txt, $scripturl, $context, $user_info, $modSettings, $smcFunc;
 
+	$context['page_title'] = $txt['mlist_search'];
+	$context['can_moderate_forum'] = allowedTo('moderate_forum');
+
 	// Can they search custom fields?
 	$request = $smcFunc['db_query']('', '
 		SELECT col_name, field_name, field_desc
@@ -443,7 +446,7 @@ function MLSearch()
 		// No fields?  Use default...
 		if (empty($_POST['fields']))
 			$_POST['fields'] = array('name');
-			
+
 		// Set defaults for how the results are sorted
 		if (!isset($_REQUEST['sort']) || !isset($context['columns'][$_REQUEST['sort']]))
 			$_REQUEST['sort'] = 'real_name';
@@ -455,18 +458,16 @@ function MLSearch()
 
 			if ((!isset($_REQUEST['desc']) && $col == $_REQUEST['sort']) || ($col != $_REQUEST['sort'] && !empty($column_details['default_sort_rev'])))
 				$context['columns'][$col]['href'] .= ';desc';
-				
+
 			if (isset($_POST['search']) && isset($_POST['fields']))
 				$context['columns'][$col]['href'] .= ';search=' . $_POST['search'] . ';fields=' . implode(',', $_POST['fields']);
 
 			$context['columns'][$col]['link'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>';
 			$context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col;
 		}
-	
+
 		// set up some things for use in the template
-		$context['page_title'] = $txt['mlist_search'];
-		$context['can_moderate_forum'] = allowedTo('moderate_forum');
-		$context['sort_direction'] = !isset($_REQUEST['desc']) ? 'up' : 'down';	
+		$context['sort_direction'] = !isset($_REQUEST['desc']) ? 'up' : 'down';
 		$context['sort_by'] = $_REQUEST['sort'];
 
 		$query_parameters = array(
@@ -500,9 +501,13 @@ function MLSearch()
 		else
 			$condition = '';
 
+		if ($smcFunc['db_case_sensitive'])
+			foreach ($fields as $key => $field)
+				$fields[$key] = 'LOWER(' . $field . ')';
+
 		$customJoin = array();
 		$customCount = 10;
-		
+
 		// Any custom fields to search for - these being tricky?
 		foreach ($_POST['fields'] as $field)
 		{
@@ -515,7 +520,7 @@ function MLSearch()
 			}
 		}
 
-		$query = $_POST['search'] == '' ? '= {string:blank_string}' : 'LIKE {string:search}';
+		$query = $_POST['search'] == '' ? '= {string:blank_string}' : ($smcFunc['db_case_sensitive'] ? 'LIKE LOWER({string:search})' : 'LIKE {string:search}');
 
 		$request = $smcFunc['db_query']('', '
 			SELECT COUNT(*)
@@ -574,7 +579,7 @@ function MLSearch()
 		'url' => $scripturl . '?action=mlist;sa=search',
 		'name' => &$context['page_title']
 	);
-	
+
 	// Highlight the correct button, too!
 	unset($context['memberlist_buttons']['view_all_members']['active']);
 	$context['memberlist_buttons']['mlist_search']['active'] = true;
@@ -598,12 +603,12 @@ function printMemberListRows($request)
 		array(
 		)
 	);
-	list ($MOST_POSTS) = $smcFunc['db_fetch_row']($result);
+	list ($most_posts) = $smcFunc['db_fetch_row']($result);
 	$smcFunc['db_free_result']($result);
 
 	// Avoid division by zero...
-	if ($MOST_POSTS == 0)
-		$MOST_POSTS = 1;
+	if ($most_posts == 0)
+		$most_posts = 1;
 
 	$members = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
@@ -619,7 +624,7 @@ function printMemberListRows($request)
 			continue;
 
 		$context['members'][$member] = $memberContext[$member];
-		$context['members'][$member]['post_percent'] = round(($context['members'][$member]['real_posts'] * 100) / $MOST_POSTS);
+		$context['members'][$member]['post_percent'] = round(($context['members'][$member]['real_posts'] * 100) / $most_posts);
 		$context['members'][$member]['registered_date'] = strftime('%Y-%m-%d', $context['members'][$member]['registered_timestamp']);
 	}
 }

+ 32 - 14
Sources/MessageIndex.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -113,7 +113,7 @@ function MessageIndex()
 		foreach ($board_info['moderators'] as $mod)
 			$context['link_moderators'][] ='<a href="' . $scripturl . '?action=profile;u=' . $mod['id'] . '" title="' . $txt['board_moderator'] . '">' . $mod['name'] . '</a>';
 
-		$context['linktree'][count($context['linktree']) - 1]['extra_after'] = ' (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')';
+		$context['linktree'][count($context['linktree']) - 1]['extra_after'] = '<span class="board_moderators"> (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')</span>';
 	}
 
 	// Mark current and parent boards as seen.
@@ -361,20 +361,22 @@ function MessageIndex()
 				' . ($user_info['is_guest'] ? '0' : 'IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1') . ' AS new_from,
 				t.id_last_msg, t.approved, t.unapproved_posts, t.id_redirect_topic, ml.poster_time AS last_poster_time,
 				ml.id_msg_modified, ml.subject AS last_subject, ml.icon AS last_icon,
-				ml.poster_name AS last_member_name, ml.id_member AS last_id_member,
+				ml.poster_name AS last_member_name, ml.id_member AS last_id_member, ' . (!empty($settings['avatars_on_indexes']) ? 'meml.avatar,' : '') . '
 				IFNULL(meml.real_name, ml.poster_name) AS last_display_name, t.id_first_msg,
 				mf.poster_time AS first_poster_time, mf.subject AS first_subject, mf.icon AS first_icon,
 				mf.poster_name AS first_member_name, mf.id_member AS first_id_member,
 				IFNULL(memf.real_name, mf.poster_name) AS first_display_name, ' . (!empty($modSettings['preview_characters']) ? '
 				SUBSTRING(ml.body, 1, ' . ($modSettings['preview_characters'] + 256) . ') AS last_body,
-				SUBSTRING(mf.body, 1, ' . ($modSettings['preview_characters'] + 256) . ') AS first_body,' : '') . 'ml.smileys_enabled AS last_smileys, mf.smileys_enabled AS first_smileys
+				SUBSTRING(mf.body, 1, ' . ($modSettings['preview_characters'] + 256) . ') AS first_body,' : '') . 'ml.smileys_enabled AS last_smileys, mf.smileys_enabled AS first_smileys' . (!empty($settings['avatars_on_indexes']) ? ',
+				IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type' : '') . '
 			FROM {db_prefix}topics AS t
 				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
 				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
 				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)
 				LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)' . ($user_info['is_guest'] ? '' : '
 				LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
-				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})'). '
+				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})') . (!empty($settings['avatars_on_indexes']) ? '
+				LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = ml.id_member)' : '') . '
 			WHERE ' . ($pre_query ? 't.id_topic IN ({array_int:topic_list})' : 't.id_board = {int:current_board}') . (!$modSettings['postmod_active'] || $context['can_approve_posts'] ? '' : '
 				AND (t.approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR t.id_member_started = {int:current_member}') . ')') . '
 			ORDER BY ' . ($pre_query ? 'FIND_IN_SET(t.id_topic, {string:find_set_topics})' : (!empty($modSettings['enableStickyTopics']) ? 'is_sticky' . ($fake_ascending ? '' : ' DESC') . ', ' : '') . $_REQUEST['sort'] . ($ascending ? '' : ' DESC')) . '
@@ -472,6 +474,24 @@ function MessageIndex()
 					$context['icon_sources'][$row['last_icon']] = 'images_url';
 			}
 
+			if (!empty($settings['avatars_on_indexes']))
+			{
+				// Allow themers to show the latest poster's avatar along with the topic
+				if(!empty($row['avatar']))
+				{
+					if ($modSettings['avatar_action_too_large'] == 'option_html_resize' || $modSettings['avatar_action_too_large'] == 'option_js_resize')
+					{
+						$avatar_width = !empty($modSettings['avatar_max_width_external']) ? ' width="' . $modSettings['avatar_max_width_external'] . '"' : '';
+						$avatar_height = !empty($modSettings['avatar_max_height_external']) ? ' height="' . $modSettings['avatar_max_height_external'] . '"' : '';
+					}
+					else
+					{
+						$avatar_width = '';
+						$avatar_height = '';
+					}
+				}
+			}
+
 			// 'Print' the topic info.
 			$context['topics'][$row['id_topic']] = array(
 				'id' => $row['id_topic'],
@@ -530,6 +550,13 @@ function MessageIndex()
 				'approved' => $row['approved'],
 				'unapproved_posts' => $row['unapproved_posts'],
 			);
+			if (!empty($settings['avatars_on_indexes']))
+				$context['topics'][$row['id_topic']]['last_post']['member']['avatar'] = array(
+					'name' => $row['avatar'],
+					'image' => $row['avatar'] == '' ? ($row['id_attach'] > 0 ? '<img class="avatar" src="' . (empty($row['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $row['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $row['filename']) . '" alt="" />' : '') : (stristr($row['avatar'], 'http://') ? '<img class="avatar" src="' . $row['avatar'] . '"' . $avatar_width . $avatar_height . ' alt="" />' : '<img class="avatar" src="' . $modSettings['avatar_url'] . '/' . htmlspecialchars($row['avatar']) . '" alt="" />'),
+					'href' => $row['avatar'] == '' ? ($row['id_attach'] > 0 ? (empty($row['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $row['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $row['filename']) : '') : (stristr($row['avatar'], 'http://') ? $row['avatar'] : $modSettings['avatar_url'] . '/' . $row['avatar']),
+					'url' => $row['avatar'] == '' ? '' : (stristr($row['avatar'], 'http://') ? $row['avatar'] : $modSettings['avatar_url'] . '/' . $row['avatar'])
+				);
 
 			determineTopicClass($context['topics'][$row['id_topic']]);
 		}
@@ -609,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']))

+ 3 - 18
Sources/ModerationCenter.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -154,21 +154,6 @@ function ModerationMain($dont_call = false)
 		),
 	);
 
-	// Any files to include for moderation?
-	if (!empty($modSettings['integrate_moderate_include']))
-	{
-		$moderate_includes = explode(',', $modSettings['integrate_moderate_include']);
-		foreach ($moderate_includes as $include)
-		{
-			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
-			if (file_exists($include))
-				require_once($include);
-		}
-	}
-
-	// Let them modify admin areas easily.
-	call_integration_hook('integrate_moderate_areas', array(&$moderation_areas));
-
 	// Make sure the administrator has a valid session...
 	validateSession('moderate');
 
@@ -225,7 +210,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';
@@ -2164,5 +2149,5 @@ function ModEndSession()
 		if (strpos($key, '-mod') !== false)
 			unset($_SESSION['token'][$key]);
 
-	redirectexit('?action=moderate');
+	redirectexit('action=moderate');
 }

+ 1 - 1
Sources/Modlog.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/MoveTopic.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 4 - 4
Sources/News.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -170,7 +170,7 @@ function ShowXmlFeed()
 	// Show in rss or proprietary format?
 	$xml_format = isset($_GET['type']) && in_array($_GET['type'], array('smf', 'rss', 'rss2', 'atom', 'rdf', 'webslice')) ? $_GET['type'] : 'smf';
 
-	// @todo Birthdays? 
+	// @todo Birthdays?
 
 	// List all the different types of data they can pull.
 	$subActions = array(
@@ -179,10 +179,10 @@ function ShowXmlFeed()
 		'members' => array('getXmlMembers', 'member'),
 		'profile' => array('getXmlProfile', null),
 	);
-	
+
 	// Easy adding of sub actions
  	call_integration_hook('integrate_xmlfeeds', array(&$subActions));
-	
+
 	if (empty($_GET['sa']) || !isset($subActions[$_GET['sa']]))
 		$_GET['sa'] = 'recent';
 

+ 1 - 1
Sources/Notify.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/PackageGet.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 61 - 34
Sources/Packages.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -625,7 +625,7 @@ function PackageInstallTest()
 				// Is the action already stated?
 				$theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
 				$action['unparsed_destination'] = $action['unparsed_filename'];
-				
+
 				// If it's not auto do we think we have something we can act upon?
 				if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
 					$theme_action = '';
@@ -645,7 +645,12 @@ function PackageInstallTest()
 		if (empty($thisAction))
 			continue;
 
-		if (!file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']))
+		if ($context['uninstalling'])
+			$file = in_array($action['type'], array('remove-dir', 'remove-file')) ? $action['filename'] : $boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'];
+		else
+			$file =  $boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'];
+
+		if (isset($action['filename']) && !file_exists($file))
 		{
 			$context['has_failure'] = true;
 
@@ -689,7 +694,7 @@ function PackageInstallTest()
 				if (isset($theme_data['theme_dir']) && $id != 1)
 				{
 					$real_path = $theme_data['theme_dir'] . $path;
-					
+
 					// Confirm that we don't already have this dealt with by another entry.
 					if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes']))
 					{
@@ -701,7 +706,7 @@ function PackageInstallTest()
 								$temp = dirname($temp);
 							$chmod_files[] = $temp;
 						}
-						
+
 						if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path))))
 							$chmod_files[] = $real_path;
 
@@ -1011,9 +1016,9 @@ function PackageInstall()
 			elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function']))
 			{
 				if ($action['reverse'])
-					remove_integration_function($action['hook'], $action['function']);
+					remove_integration_function($action['hook'], $action['function'], $action['include_file']);
 				else
-					add_integration_function($action['hook'], $action['function']);
+					add_integration_function($action['hook'], $action['function'], $action['include_file']);
 			}
 			// Only do the database changes on uninstall if requested.
 			elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes'])))
@@ -1359,11 +1364,11 @@ function PackageBrowse()
 
 	$context['page_title'] .= ' - ' . $txt['browse_packages'];
 
-	$context['forum_version'] = $forum_version;
-	$context['modification_types'] = array('modification', 'avatar', 'language', 'unknown');
-
 	$installed = $context['sub_action'] == 'installed' ? true : false;
 
+	$context['forum_version'] = $forum_version;
+	$context['modification_types'] = $installed ? array('modification') : array('modification', 'avatar', 'language', 'unknown');
+
 	require_once($sourcedir . '/Subs-List.php');
 
 	foreach ($context['modification_types'] as $type)
@@ -1377,13 +1382,13 @@ function PackageBrowse()
 				'function' => 'list_getPackages',
 				'params' => array('type' => $type, 'installed' => $installed),
 			),
-			'base_href' => $scripturl . '?action=admin;area=packages;sa=browse;type=' . $type,
+			'base_href' => $scripturl . '?action=admin;area=packages;sa=' . $context['sub_action'] . ';type=' . $type,
 			'default_sort_col' => 'id' . $type,
 			'columns' => array(
 				'id' . $type => array(
 					'header' => array(
 						'value' => $txt['package_id'],
-						'style' => 'width: 32px;',
+						'style' => 'width: 40px;',
 					),
 					'data' => array(
 						'function' => create_function('$package_md5', '
@@ -1391,11 +1396,6 @@ function PackageBrowse()
 
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
 								return $context[\'available_' . $type . '\'][$package_md5][\'sort_id\'];
-							return $context[\'sort_id\'];
-							if (empty($packageCounter))
-								$packageCounter = 1;
-
-							return $packageCounter++ . \'.\';
 						'),
 					),
 					'sort' => array(
@@ -1413,8 +1413,8 @@ function PackageBrowse()
 							global $context;
 
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
-								return $context[\'available_' . $type . '\'][$package_md5][\'name\'];'
-						),
+								return $context[\'available_' . $type . '\'][$package_md5][\'name\'];
+						'),
 					),
 					'sort' => array(
 						'default' => 'name',
@@ -1431,8 +1431,8 @@ function PackageBrowse()
 							global $context;
 
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
-								return $context[\'available_' . $type . '\'][$package_md5][\'version\'];'
-						),
+								return $context[\'available_' . $type . '\'][$package_md5][\'version\'];
+						'),
 					),
 					'sort' => array(
 						'default' => 'version',
@@ -1472,8 +1472,8 @@ function PackageBrowse()
 
 							return $return . \'
 									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=list;package=\' . $package[\'filename\'] . \'">[ \' . $txt[\'list_files\'] . \' ]</a>
-									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=remove;package=\' . $package[\'filename\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'"\' . ($package[\'is_installed\'] && $package[\'is_current\'] ? \' onclick="return confirm(\\\'\' . $txt[\'package_delete_bad\'] . \'\\\');"\' : \'\') . \'>[ \' . $txt[\'package_delete\'] . \' ]</a>\';'
-							),
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=remove;package=\' . $package[\'filename\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'"\' . ($package[\'is_installed\'] && $package[\'is_current\'] ? \' onclick="return confirm(\\\'\' . $txt[\'package_delete_bad\'] . \'\\\');"\' : \'\') . \'>[ \' . $txt[\'package_delete\'] . \' ]</a>\';
+						'),
 						'style' => 'text-align: right;',
 					),
 				),
@@ -1517,7 +1517,11 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 	$the_version = strtr($forum_version, array('SMF ' => ''));
 
 	// Here we have a little code to help those who class themselves as something of gods, version emulation ;)
-	if (isset($_GET['version_emulate']))
+	if (isset($_GET['version_emulate']) && strtr($_GET['version_emulate'], array('SMF ' => '')) == $the_version)
+	{
+		unset($_SESSION['version_emulate']);
+	}
+	elseif (isset($_GET['version_emulate']))
 	{
 		if (($_GET['version_emulate'] === 0 || $_GET['version_emulate'] === $forum_version) && isset($_SESSION['version_emulate']))
 			unset($_SESSION['version_emulate']);
@@ -1549,10 +1553,11 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 
 	if ($installed)
 	{
+		$sort_id = 1;
 		foreach ($instmods as $installed_mod)
 		{
-			$packages['modification'][] = $installed_mod['package_id'];
 			$context['available_modification'][$installed_mod['package_id']] = array(
+				'sort_id' => $sort_id++,
 				'can_uninstall' => true,
 				'name' => $installed_mod['name'],
 				'filename' => $installed_mod['filename'],
@@ -1562,16 +1567,22 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 				'is_current' => true,
 			);
 		}
-		return $packages['modification'];
 	}
 
 	if (empty($packages))
-		$packages = array('modification' => array(), 'avatar' => array(), 'language' => array(), 'unknown' => array());
+		foreach ($context['modification_types'] as $type)
+			$packages[$type] = array();
 
 	if ($dir = @opendir($boarddir . '/Packages'))
 	{
 		$dirs = array();
-		$sort_id = 1;
+		$sort_id = array(
+			'mod' => 1,
+			'modification' => 1,
+			'avatar' => 1,
+			'language' => 1,
+			'unknown' => 1,
+		);
 		while ($package = readdir($dir))
 		{
 			if ($package == '.' || $package == '..' || $package == 'temp' || (!(is_dir($boarddir . '/Packages/' . $package) && file_exists($boarddir . '/Packages/' . $package . '/package-info.xml')) && substr(strtolower($package), -7) != '.tar.gz' && substr(strtolower($package), -4) != '.tgz' && substr(strtolower($package), -4) != '.zip'))
@@ -1613,7 +1624,7 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 			{
 				$packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0;
 
-				$packageInfo['sort_id'] = $sort_id++;
+				$packageInfo['sort_id'] = $sort_id[$packageInfo['type']];
 				$packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]);
 				$packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']);
 				$packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']);
@@ -1643,7 +1654,7 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 					if ($packageInfo['can_install'] === false && $install->exists('@for') && empty($_SESSION['version_emulate']))
 					{
 						$reset = true;
-						
+
 						// Get the highest install version that is available from the package
 						foreach ($installs as $install)
 						{
@@ -1683,12 +1694,12 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 							break;
 						}
 					}
-					
+
 					// no uninstall found for this version, lets see if one exists for another
 					if ($packageInfo['can_uninstall'] === false && $uninstall->exists('@for') && empty($_SESSION['version_emulate']))
 					{
 						$reset = true;
-						
+
 						// Get the highest install version that is available from the package
 						foreach ($uninstalls as $uninstall)
 						{
@@ -1701,24 +1712,40 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 				// Modification.
 				if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod')
 				{
-					$packages['modification'][strtolower($packageInfo[$sort])] = md5($package);
-					$context['available_modification'][md5($package)] = $packageInfo;
+					$sort_id['modification']++;
+					$sort_id['mod']++;
+					if ($installed)
+					{
+						if (!empty($context['available_modification'][$packageInfo['id']]))
+						{
+							$packages['modification'][strtolower($packageInfo[$sort]) . '_' . $sort_id['mod']] = $packageInfo['id'];
+							$context['available_modification'][$packageInfo['id']] = array_merge($context['available_modification'][$packageInfo['id']], $packageInfo);
+						}
+					}
+					else
+					{
+						$packages['modification'][strtolower($packageInfo[$sort]) .  '_' . $sort_id['mod']] = md5($package);
+						$context['available_modification'][md5($package)] = $packageInfo;
+					}
 				}
 				// Avatar package.
 				elseif ($packageInfo['type'] == 'avatar')
 				{
+					$sort_id[$packageInfo['type']]++;
 					$packages['avatar'][strtolower($packageInfo[$sort])] = md5($package);
 					$context['available_avatar'][md5($package)] = $packageInfo;
 				}
 				// Language package.
 				elseif ($packageInfo['type'] == 'language')
 				{
+					$sort_id[$packageInfo['type']]++;
 					$packages['language'][strtolower($packageInfo[$sort])] = md5($package);
 					$context['available_language'][md5($package)] = $packageInfo;
 				}
 				// Other stuff.
 				else
 				{
+					$sort_id['unknown']++;
 					$packages['unknown'][strtolower($packageInfo[$sort])] = md5($package);
 					$context['available_unknown'][md5($package)] = $packageInfo;
 				}

+ 68 - 14
Sources/PersonalMessage.php

@@ -9,7 +9,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -35,7 +35,7 @@ function MessageMain()
 	// This file contains the basic functions for sending a PM.
 	require_once($sourcedir . '/Subs-Post.php');
 
-	loadLanguage('PersonalMessage');
+	loadLanguage('PersonalMessage+Drafts');
 
 	if (WIRELESS && WIRELESS_PROTOCOL == 'wap')
 		fatal_lang_error('wireless_error_notyet', false);
@@ -197,6 +197,7 @@ function MessageMain()
 		'send' => 'MessagePost',
 		'send2' => 'MessagePost2',
 		'settings' => 'MessageSettings',
+		'showpmdrafts' => 'MessageDrafts',
 	);
 
 	if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
@@ -210,7 +211,7 @@ function MessageMain()
 }
 
 /**
- * A sidebar to easily access different areas of the section
+ * A menu to easily access different areas of the PM section
  *
  * @param string $area
  */
@@ -235,6 +236,12 @@ function messageIndexBar($area)
 					'label' => $txt['sent_items'],
 					'custom_url' => $scripturl . '?action=pm;f=sent',
 				),
+				'drafts' => array(
+					'label' => $txt['drafts_show'],
+					'custom_url' => $scripturl . '?action=pm;sa=showpmdrafts',
+					'permission' => allowedTo('pm_draft'),
+					'enabled' => !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_pm_enabled']),
+				),
 			),
 		),
 		'labels' => array(
@@ -332,22 +339,25 @@ function messageIndexBar($area)
 	$menuOptions = array(
 		'current_area' => $area,
 		'disable_url_session_check' => true,
-		'toggle_url' => $current_page . ';togglebar',
-		'toggle_redirect_url' => $current_page,
 	);
 
 	// Actually create the menu!
 	$pm_include_data = createMenu($pm_areas, $menuOptions);
 	unset($pm_areas);
 
+	// No menu means no access.
+	if (!$pm_include_data && (!$user_info['is_guest'] || validateSession()))
+		fatal_lang_error('no_access', false);
+
 	// Make a note of the Unique ID for this menu.
 	$context['pm_menu_id'] = $context['max_menu_id'];
 	$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
 
 	// Set the selected item.
-	$context['menu_item_selected'] = $pm_include_data['current_area'];
+	$current_area = $pm_include_data['current_area'];
+	$context['menu_item_selected'] = $current_area;
 
-	// obExit will know what to do!
+	// Set the template for this area and add the profile layer.
 	if (!WIRELESS && !isset($_REQUEST['xml']))
 		$context['template_layers'][] = 'pm';
 }
@@ -843,7 +853,7 @@ function MessageFolder()
 		elseif (!empty($context['current_pm']))
 			markMessages($display_pms, $context['current_label_id']);
 	}
-	
+
 	// Build the conversation button array.
 	if ($context['display_mode'] == 2)
 	{
@@ -851,7 +861,7 @@ function MessageFolder()
 			'reply' => array('text' => 'reply_to_all', 'image' => 'reply.png', 'lang' => true, 'url' => $scripturl . '?action=pm;sa=send;f=' . $context['folder'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pmsg=' . $context['current_pm'] . ';u=all', 'active' => true),
 			'delete' => array('text' => 'delete_conversation', 'image' => 'delete.png', 'lang' => true, 'url' => $scripturl . '?action=pm;sa=pmactions;pm_actions[' . $context['current_pm'] . ']=delete;conversation;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'], 'custom' => 'onclick="return confirm(\'' . addslashes($txt['remove_message']) . '?\');"'),
 		);
-		
+
 		// Allow mods to add additional buttons here
 		call_integration_hook('integrate_conversation_buttons');
 	}
@@ -1771,6 +1781,18 @@ function MessagePost()
 
 	$modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']);
 
+	// Are PM drafts enabled?
+	$context['drafts_pm_save'] = !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
+	$context['drafts_autosave'] = !empty($context['drafts_pm_save']) && !empty($modSettings['drafts_autosave_enabled']) && allowedTo('pm_autosave_draft');
+
+	// Generate a list of drafts that they can load in to the editor
+	if (!empty($context['drafts_pm_save']))
+	{
+		require_once($sourcedir . '/Drafts.php');
+		$pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
+		ShowDrafts($user_info['id'], $pm_seed, 1);
+	}
+
 	// Needed for the WYSIWYG editor.
 	require_once($sourcedir . '/Subs-Editor.php');
 
@@ -1806,6 +1828,28 @@ function MessagePost()
 	checkSubmitOnce('register');
 }
 
+/**
+ * This function allows the user to view their PM drafts
+ */
+function MessageDrafts()
+{
+	global $context, $sourcedir, $user_info, $modSettings;
+
+	// Set draft capability
+	$context['drafts_pm_save'] = !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
+	$context['drafts_autosave'] = !empty($context['drafts_pm_save']) && !empty($modSettings['drafts_autosave_enabled']) && allowedTo('pm_autosave_draft');
+
+	// validate with loadMemberData()
+	$memberResult = loadMemberData($user_info['id'], false);
+	if (!is_array($memberResult))
+		fatal_lang_error('not_a_user', false);
+	list ($memID) = $memberResult;
+
+	// drafts is where the functions reside
+	require_once($sourcedir . '/Drafts.php');
+	showPMDrafts($memID);
+}
+
 /**
  * An error in the message...
  *
@@ -1922,9 +1966,9 @@ function messagePostError($error_types, $named_recipients, $recipient_ids = arra
 
 	// Set each of the errors for the template.
 	loadLanguage('Errors');
-	
+
 	$context['error_type'] = 'minor';
-	
+
 	$context['post_error'] = array(
 		'messages' => array(),
 		// @todo error handling: maybe fatal errors can be error_type => serious
@@ -1940,12 +1984,16 @@ function messagePostError($error_types, $named_recipients, $recipient_ids = arra
 				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
 			$context['post_error']['messages'][] = $txt['error_' . $error_type];
 		}
-		
+
 		// If it's not a minor error flag it as such.
 		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
 			$context['error_type'] = 'serious';
 	}
 
+	// Need to reset draft capability once again
+	$context['drafts_pm_save'] = !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
+	$context['drafts_autosave'] = !empty($context['drafts_pm_save']) && !empty($modSettings['drafts_autosave_enabled']) && allowedTo('pm_autosave_draft');
+
 	// We need to load the editor once more.
 	require_once($sourcedir . '/Subs-Editor.php');
 
@@ -2162,9 +2210,7 @@ function MessagePost2()
 		$context['require_verification'] = create_control_verification($verificationOptions, true);
 
 		if (is_array($context['require_verification']))
-		{
 			$post_errors = array_merge($post_errors, $context['require_verification']);
-		}
 	}
 
 	// If they did, give a chance to make ammends.
@@ -2207,6 +2253,14 @@ function MessagePost2()
 		return messagePostError(array(), $namedRecipientList, $recipientList);
 	}
 
+	// Want to save this as a draft and think about it some more?
+	if (!empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_pm_enabled']) && isset($_POST['save_draft']))
+	{
+		require_once($sourcedir . '/Drafts.php');
+		SavePMDraft($post_errors, $recipientList);
+		return messagePostError($post_errors, $namedRecipientList, $recipientList);
+	}
+
 	// Before we send the PM, let's make sure we don't have an abuse of numbers.
 	elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
 	{

+ 4 - 4
Sources/Poll.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -213,7 +213,7 @@ function Vote()
 		$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
 		smf_setcookie('guest_poll_vote', $_COOKIE['guest_poll_vote'], time() + 2500000, $cookie_url[1], $cookie_url[0], false, false);
 	}
-	
+
 	// Maybe let a social networking mod log this, or something?
 	call_integration_hook('integrate_poll_vote', array(&$row['id_poll'], &$pollOptions));
 
@@ -882,7 +882,7 @@ function EditPoll2()
 			)
 		);
 	}
-	
+
 	call_integration_hook('integrate_poll_add_edit', array($bcinfo['id_poll'], $isEdit));
 
 	// Off we go.
@@ -976,7 +976,7 @@ function RemovePoll()
 			'no_poll' => 0,
 		)
 	);
-	
+
 	// A mod might have logged this (social network?), so let them remove, it too
 	call_integration_hook('integrate_poll_remove', array(&$pollID));
 

+ 91 - 252
Sources/Post.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -18,7 +18,8 @@ if (!defined('SMF'))
 	die('Hacking attempt...');
 
 /**
- * handles showing the post screen, loading the post to be modified, and loading any post quoted.
+ * Handles showing the post screen, loading the post to be modified, and loading any post quoted.
+ *
  * - additionally handles previews of posts.
  * - @uses the Post template and language file, main sub template.
  * - allows wireless access using the protocol_post sub template.
@@ -48,18 +49,6 @@ function Post($post_errors = array())
 
 	require_once($sourcedir . '/Subs-Post.php');
 
-	// Any files to include for post?
-	if (!isset($post_includes) && !empty($modSettings['integrate_post_include']))
-	{
-		$post_includes = explode(',', $modSettings['integrate_post_include']);
-		foreach ($post_includes as $include)
-		{
-			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
-			if (file_exists($include))
-				require_once($include);
-		}
-	}
-
 	if (isset($_REQUEST['xml']))
 	{
 		$context['sub_template'] = 'post';
@@ -464,7 +453,7 @@ function Post($post_errors = array())
 		}
 
 		// Only show the preview stuff if they hit Preview.
-		if ($really_previewing == true || isset($_REQUEST['xml']))
+		if (($really_previewing == true || isset($_REQUEST['xml'])) && !isset($_POST['id_draft']))
 		{
 			// Set up the preview message and subject and censor them...
 			$context['preview_message'] = $form_message;
@@ -509,16 +498,18 @@ function Post($post_errors = array())
 					m.poster_name, m.poster_email, m.subject, m.icon, m.approved,
 					IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach,
 					a.approved AS attachment_approved, t.id_member_started AS id_member_poster,
-					m.poster_time
+					m.poster_time, log.id_action
 				FROM {db_prefix}messages AS m
 					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
 					LEFT JOIN {db_prefix}attachments AS a ON (a.id_msg = m.id_msg AND a.attachment_type = {int:attachment_type})
+					LEFT JOIN {db_prefix}log_actions AS log ON (m.id_topic = log.id_topic AND log.action = {string:announce_action})
 				WHERE m.id_msg = {int:id_msg}
 					AND m.id_topic = {int:current_topic}',
 				array(
 					'current_topic' => $topic,
 					'attachment_type' => 0,
 					'id_msg' => $_REQUEST['msg'],
+					'announce_action' => 'announce_topic',
 				)
 			);
 			// The message they were trying to edit was most likely deleted.
@@ -575,6 +566,12 @@ function Post($post_errors = array())
 				$smcFunc['db_free_result']($request);
 			}
 
+			if ($context['can_announce'] && !empty($row['id_action']))
+			{
+				loadLanguage('Errors');
+				$context['post_error']['messages'][] = $txt['error_topic_already_announced'];
+			}
+
 			// Allow moderators to change names....
 			if (allowedTo('moderate_forum') && !empty($topic))
 			{
@@ -615,22 +612,23 @@ function Post($post_errors = array())
 				m.poster_name, m.poster_email, m.subject, m.icon, m.approved,
 				IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach,
 				a.approved AS attachment_approved, t.id_member_started AS id_member_poster,
-				m.poster_time
+				m.poster_time, log.id_action
 			FROM {db_prefix}messages AS m
 				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
 				LEFT JOIN {db_prefix}attachments AS a ON (a.id_msg = m.id_msg AND a.attachment_type = {int:attachment_type})
+					LEFT JOIN {db_prefix}log_actions AS log ON (m.id_topic = log.id_topic AND log.action = {string:announce_action})
 			WHERE m.id_msg = {int:id_msg}
 				AND m.id_topic = {int:current_topic}',
 			array(
 				'current_topic' => $topic,
 				'attachment_type' => 0,
 				'id_msg' => $_REQUEST['msg'],
+				'announce_action' => 'announce_topic',
 			)
 		);
 		// The message they were trying to edit was most likely deleted.
-		//@todo Change this error message?
 		if ($smcFunc['db_num_rows']($request) == 0)
-			fatal_lang_error('no_board', false);
+			fatal_lang_error('no_message', false);
 		$row = $smcFunc['db_fetch_assoc']($request);
 
 		$attachment_stuff = array($row);
@@ -653,6 +651,12 @@ function Post($post_errors = array())
 		else
 			isAllowedTo('modify_any');
 
+		if ($context['can_announce'] && !empty($row['id_action']))
+		{
+			loadLanguage('Errors');
+			$context['post_error']['messages'][] = $txt['error_topic_already_announced'];
+		}
+
 		// When was it last modified?
 		if (!empty($row['modified_time']))
 			$context['last_modified'] = timeformat($row['modified_time']);
@@ -799,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']))
@@ -913,12 +906,12 @@ function Post($post_errors = array())
 				// Show any errors which might of occured.
 				if (!empty($attachment['errors']))
 				{
-					$txt['error_attach_errrors'] = empty($txt['error_attach_errrors']) ? '<br />' : '';
-					$txt['error_attach_errrors'] .= vsprintf($txt['attach_warning'], $attachment['name']) . '<div style="padding: 0 1em;">';
+					$txt['error_attach_errors'] = empty($txt['error_attach_errors']) ? '<br />' : '';
+					$txt['error_attach_errors'] .= vsprintf($txt['attach_warning'], $attachment['name']) . '<div style="padding: 0 1em;">';
 					foreach ($attachment['errors'] as $error)
-						$txt['error_attach_errrors'] .= (is_array($error) ? vsprintf($txt[$error[0]], $error[1]) : $txt[$error]) . '<br  />';
-					$txt['error_attach_errrors'] .= '</div>';
-					$post_errors[] = 'attach_errrors';
+						$txt['error_attach_errors'] .= (is_array($error) ? vsprintf($txt[$error[0]], $error[1]) : $txt[$error]) . '<br  />';
+					$txt['error_attach_errors'] .= '</div>';
+					$post_errors[] = 'attach_errors';
 
 					// Take out the trash.
 					unset($_SESSION['temp_attachments'][$attachID]);
@@ -1035,6 +1028,17 @@ function Post($post_errors = array())
 	$context['subject'] = addcslashes($form_subject, '"');
 	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
 
+	// Are post drafts enabled?
+	$context['drafts_save'] = !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft');
+	$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']) && allowedTo('post_autosave_draft');
+
+	// Build a list of drafts that they can load in to the editor
+	if (!empty($context['drafts_save']))
+	{
+		require_once($sourcedir . '/Drafts.php');
+		ShowDrafts($user_info['id'], $topic);
+	}
+
 	// Needed for the editor and message icons.
 	require_once($sourcedir . '/Subs-Editor.php');
 
@@ -1143,7 +1147,8 @@ function Post($post_errors = array())
 }
 
 /**
- * actually posts or saves the message composed with Post().
+ * Posts or saves the message composed with Post().
+ *
  * requires various permissions depending on the action.
  * handles attachment, post, and calendar saving.
  * sends off notifications, and allows for announcements and moderation.
@@ -1168,22 +1173,6 @@ function Post2()
 	// No need!
 	$context['robot_no_index'] = true;
 
-	// Any files to include for post?
-	if (!empty($modSettings['integrate_post_include']))
-	{
-		$post_includes = explode(',', $modSettings['integrate_post_include']);
-		foreach ($post_includes as $include)
-		{
-			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
-			if (file_exists($include))
-				require_once($include);
-		}
-	}
-
-	// Previewing? Go back to start.
-	if (isset($_REQUEST['preview']))
-		return Post();
-
 	// Previewing? Go back to start.
 	if (isset($_REQUEST['preview']))
 		return Post();
@@ -1213,6 +1202,10 @@ function Post2()
 	require_once($sourcedir . '/Subs-Post.php');
 	loadLanguage('Post');
 
+	// Drafts enabled?
+	if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		require_once($sourcedir . '/Drafts.php');
+
 	// First check to see if they are trying to delete any current attachments.
 	if (isset($_POST['attach_del']))
 	{
@@ -1250,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))
@@ -1512,6 +1325,13 @@ function Post2()
 		if (isset($_POST['sticky']) && (empty($modSettings['enableStickyTopics']) || $_POST['sticky'] == $topic_info['is_sticky'] || !allowedTo('make_sticky')))
 			unset($_POST['sticky']);
 
+		// If drafts are enabled, then pass this off
+		if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		{
+			SaveDraft($post_errors);
+			return Post();
+		}
+
 		// If the number of replies has changed, if the setting is enabled, go back to Post() - which handles the error.
 		if (empty($options['no_new_reply_warning']) && isset($_POST['last_msg']) && $topic_info['id_last_msg'] > $_POST['last_msg'])
 		{
@@ -1550,6 +1370,13 @@ function Post2()
 		if (isset($_POST['sticky']) && (empty($modSettings['enableStickyTopics']) || empty($_POST['sticky']) || !allowedTo('make_sticky')))
 			unset($_POST['sticky']);
 
+		// Saving your new topic as a draft first?
+		if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		{
+			SaveDraft($post_errors);
+			return Post();
+		}
+
 		$posterIsGuest = $user_info['is_guest'];
 	}
 	// Modifying an existing message?
@@ -1626,6 +1453,13 @@ function Post2()
 				$moderationAction = true;
 		}
 
+		// If drafts are enabled, then lets send this off to save
+		if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		{
+			SaveDraft($post_errors);
+			return Post();
+		}
+
 		$posterIsGuest = empty($row['id_member']);
 
 		// Can they approve it?
@@ -1856,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'],
 			);
@@ -1869,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))
@@ -1931,7 +1763,7 @@ function Post2()
 			$pollOptions,
 			array('id_poll', 'id_choice')
 		);
-		
+
 		call_integration_hook('integrate_poll_add_edit', array($id_poll, false));
 	}
 	else
@@ -2185,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',
@@ -2212,7 +2045,8 @@ function Post2()
 }
 
 /**
- * handle the announce topic function (action=announce).
+ * Handle the announce topic function (action=announce).
+ *
  * checks the topic announcement permissions and loads the announcement template.
  * requires the announce_topic permission.
  * uses the ManageMembers template and Post language file.
@@ -2245,6 +2079,7 @@ function AnnounceTopic()
 
 /**
  * Allow a user to chose the membergroups to send the announcement to.
+ *
  * lets the user select the membergroups that will receive the topic announcement.
  */
 function AnnouncementSelectMembergroup()
@@ -2323,6 +2158,7 @@ function AnnouncementSelectMembergroup()
 
 /**
  * Send the announcement in chunks.
+ *
  * splits the members to be sent a topic announcement into chunks.
  * composes notification messages in all languages needed.
  * does the actual sending of the topic announcements in chunks.
@@ -2335,9 +2171,6 @@ function AnnouncementSend()
 
 	checkSession();
 
-	// @todo Might need an interface?
-	$chunkSize = empty($modSettings['mail_queue']) ? 50 : 500;
-
 	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
 	$groups = array_merge($board_info['groups'], array(1));
 
@@ -2377,26 +2210,27 @@ function AnnouncementSend()
 	$request = $smcFunc['db_query']('', '
 		SELECT mem.id_member, mem.email_address, mem.lngfile
 		FROM {db_prefix}members AS mem
-		WHERE mem.id_member != {int:current_member}' . (!empty($modSettings['allow_disableAnnounce']) ? '
+		WHERE (mem.id_group IN ({array_int:group_list}) OR mem.id_post_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:additional_group_list}, mem.additional_groups) != 0)' . (!empty($modSettings['allow_disableAnnounce']) ? '
 			AND mem.notify_announcements = {int:notify_announcements}' : '') . '
 			AND mem.is_activated = {int:is_activated}
-			AND (mem.id_group IN ({array_int:group_list}) OR mem.id_post_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:additional_group_list}, mem.additional_groups) != 0)
 			AND mem.id_member > {int:start}
 		ORDER BY mem.id_member
-		LIMIT ' . $chunkSize,
+		LIMIT {int:chunk_size}',
 		array(
-			'current_member' => $user_info['id'],
 			'group_list' => $_POST['who'],
 			'notify_announcements' => 1,
 			'is_activated' => 1,
 			'start' => $context['start'],
 			'additional_group_list' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $_POST['who']),
+			// @todo Might need an interface?
+			'chunk_size' => empty($modSettings['mail_queue']) ? 50 : 500,
 		)
 	);
 
 	// All members have received a mail. Go to the next screen.
 	if ($smcFunc['db_num_rows']($request) == 0)
 	{
+		logAction('announce_topic', array('topic' => $topic), 'user');
 		if (!empty($_REQUEST['move']) && allowedTo('move_any'))
 			redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback'));
 		elseif (!empty($_REQUEST['goback']))
@@ -2451,8 +2285,8 @@ function AnnouncementSend()
 }
 
 /**
- * notifies members who have requested notification for new topics
- * * posted on a board of said posts.
+ * Notifies members who have requested notification for new topics posted on a board of said posts.
+ *
  * receives data on the topics to send out notifications to by the passed in array.
  * only sends notifications to those who can *currently* see the topic (it doesn't matter if they could when they requested notification.)
  * loads the Post language file multiple times for each language if the userLanguage setting is set.
@@ -2610,6 +2444,7 @@ function notifyMembersBoard(&$topicData)
 
 /**
  * Get the topic for display purposes.
+ *
  * gets a summary of the most recent posts in a topic.
  * depends on the topicSummaryPosts setting.
  * if you are editing a post, only shows posts previous to that post.
@@ -2669,7 +2504,7 @@ function getTopic()
 }
 
 /**
- * loads a post an inserts it into the current editing text box.
+ * Loads a post an inserts it into the current editing text box.
  * uses the Post language file.
  * uses special (sadly browser dependent) javascript to parse entities for internationalization reasons.
  * accessed with ?action=quotefast.
@@ -2772,6 +2607,10 @@ function QuoteFast()
 		);
 }
 
+/**
+ * Used to edit the body or subject of a message inline
+ * called from action=jsmodify from script and topic js
+ */
 function JavaScriptModify()
 {
 	global $sourcedir, $modSettings, $board, $topic, $txt;

+ 4 - 4
Sources/PostModeration.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -522,7 +522,7 @@ function UnapprovedAttachments()
 /**
  * Callback function for UnapprovedAttachments
  * retrieve all the attachments waiting for approval the approver can approve
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort
@@ -602,7 +602,7 @@ function list_getUnapprovedAttachments($start, $items_per_page, $sort, $approve_
 /**
  * Callback function for UnapprovedAttachments
  * count all the attachments waiting for approval the approver can approve
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort
@@ -795,4 +795,4 @@ function removeMessages($messages, $messageDetails, $current_view = 'replies')
 				(empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $messageDetails[$post]['board'] ? 'topic' : 'old_topic_id') => $messageDetails[$post]['topic'], 'subject' => $messageDetails[$post]['subject'], 'member' => $messageDetails[$post]['member'], 'board' => $messageDetails[$post]['board']));
 		}
 	}
-}
+}

+ 1 - 1
Sources/Printpage.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Profile-Actions.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 23 - 4
Sources/Profile-Modify.php

@@ -9,7 +9,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -82,6 +82,9 @@ function loadProfileFields($force_reload = false)
 			'permission' => 'profile_extra',
 			'input_validate' => create_function('&$value', '
 				$value = strtr($value, \' \', \'+\');
+				if (strlen($value) > 32)
+					return \'aim_too_long\';
+
 				return true;
 			'),
 		),
@@ -433,6 +436,14 @@ function loadProfileFields($force_reload = false)
 			'input_attr' => array('maxlength="50"'),
 			'size' => 50,
 			'permission' => 'profile_extra',
+			'input_validate' => create_function('&$value', '
+				global $smcFunc;
+
+				if ($smcFunc[\'strlen\']($value) > 50)
+					return \'personal_text_too_long\';
+
+				return true;
+			'),
 		),
 		// This does ALL the pm settings
 		'pm_prefs' => array(
@@ -635,6 +646,14 @@ function loadProfileFields($force_reload = false)
 			'size' => 50,
 			'permission' => 'profile_title',
 			'enabled' => !empty($modSettings['titlesEnable']),
+			'input_validate' => create_function('&$value', '
+				global $smcFunc;
+
+				if ($smcFunc[\'strlen\'] > 50)
+					return \'user_title_too_long\';
+
+				return true;
+			'),
 		),
 		'website_title' => array(
 			'type' => 'text',
@@ -1342,7 +1361,7 @@ function editBuddies($memID)
 	if (isset($_GET['remove']))
 	{
 		checkSession('get');
-		
+
 		call_integration_hook('integrate_remove_buddy', array($memID));
 
 		// Heh, I'm lazy, do it the easy way...
@@ -1373,7 +1392,7 @@ function editBuddies($memID)
 			if (strlen($new_buddies[$k]) == 0 || in_array($new_buddies[$k], array($user_profile[$memID]['member_name'], $user_profile[$memID]['real_name'])))
 				unset($new_buddies[$k]);
 		}
-		
+
 		call_integration_hook('integrate_add_buddies', array($memID, &$new_buddies));
 
 		if (!empty($new_buddies))
@@ -1437,7 +1456,7 @@ function editBuddies($memID)
 		loadMemberContext($buddy);
 		$context['buddies'][$buddy] = $memberContext[$buddy];
 	}
-	
+
 	call_integration_hook('integrate_view_buddies', array($memID));
 }
 

+ 28 - 22
Sources/Profile-View.php

@@ -5,7 +5,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -184,7 +184,7 @@ function summary($memID)
 /**
  * Show all posts by the current user
  * @todo This function needs to be split up properly.
- * 
+ *
  * @param int $memID id_member
  */
 function showPosts($memID)
@@ -510,7 +510,7 @@ function showPosts($memID)
 
 /**
  * Show all the attachments of a user.
- * 
+ *
  * @param int $memID id_member
  */
 function showAttachments($memID)
@@ -520,7 +520,7 @@ function showAttachments($memID)
 
 	// OBEY permissions!
 	$boardsAllowed = boardsAllowedTo('view_attachments');
-	
+
 	// Make sure we can't actually see anything...
 	if (empty($boardsAllowed))
 		$boardsAllowed = array(-1);
@@ -576,6 +576,7 @@ function showAttachments($memID)
 				),
 				'data' => array(
 					'db' => 'downloads',
+					'comma_format' => true,
 				),
 				'sort' => array(
 					'default' => 'a.downloads',
@@ -603,6 +604,11 @@ function showAttachments($memID)
 				),
 				'data' => array(
 					'db' => 'posted',
+					'timeformat' => true,
+				),
+				'sort' => array(
+					'default' => 'm.poster_time',
+					'reverse' => 'm.poster_time DESC',
 				),
 			),
 		),
@@ -651,7 +657,7 @@ function list_getAttachments($start, $items_per_page, $sort, $boardsAllowed, $me
 			'filename' => $row['filename'],
 			'downloads' => $row['downloads'],
 			'subject' => censorText($row['subject']),
-			'posted' => timeformat($row['poster_time']),
+			'posted' => $row['poster_time'],
 			'msg' => $row['id_msg'],
 			'topic' => $row['id_topic'],
 			'board' => $row['id_board'],
@@ -697,7 +703,7 @@ function list_getNumAttachments($boardsAllowed, $memID)
 
 /**
  * Gets the user stats for display
- * 
+ *
  * @param int $memID id_member
  */
 function statPanel($memID)
@@ -882,14 +888,14 @@ function statPanel($memID)
 
 	// Put it in the right order.
 	ksort($context['posts_by_time']);
-	
+
 	// Custom stats (just add a template_layer to add it to the template!)
  	call_integration_hook('integrate_profile_stats', array($memID));
 }
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $memID id_member
  */
 function tracking($memID)
@@ -935,7 +941,7 @@ function tracking($memID)
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $memID id_member
  */
 function trackActivity($memID)
@@ -1156,7 +1162,7 @@ function trackActivity($memID)
 
 /**
  * Get the number of user errors
- * 
+ *
  * @param string $where
  * @param array $where_vars = array()
  * @return string number of user errors
@@ -1180,7 +1186,7 @@ function list_getUserErrorCount($where, $where_vars = array())
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort
@@ -1223,7 +1229,7 @@ function list_getUserErrors($start, $items_per_page, $sort, $where, $where_vars
 
 /**
  * @todo needs a description
- * 
+ *
  * @param string $where
  * @param array $where_vars
  * @return string count of messages matching the IP
@@ -1248,7 +1254,7 @@ function list_getIPMessageCount($where, $where_vars = array())
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort
@@ -1297,7 +1303,7 @@ function list_getIPMessages($start, $items_per_page, $sort, $where, $where_vars
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $memID = 0 id_member
  */
 function TrackIP($memID = 0)
@@ -1580,7 +1586,7 @@ function TrackIP($memID = 0)
 
 /**
  * Tracks a users logins.
- * 
+ *
  * @param int $memID = 0 id_member
  */
 function TrackLogins($memID = 0)
@@ -1659,7 +1665,7 @@ function TrackLogins($memID = 0)
 
 /**
  * Callback for trackLogins for counting history.
- * 
+ *
  * @param string $where
  * @param array $where_vars
  * @return string count of messages matching the IP
@@ -1685,7 +1691,7 @@ function list_getLoginCount($where, $where_vars = array())
 
 /**
  * Callback for trackLogins data.
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort
@@ -1720,7 +1726,7 @@ function list_getLogins($start, $items_per_page, $sort, $where, $where_vars = ar
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $memID id_member
  */
 function trackEdits($memID)
@@ -1821,7 +1827,7 @@ function trackEdits($memID)
 
 /**
  * How many edits?
- * 
+ *
  * @param int $memID id_member
  * @return string number of profile edits
  */
@@ -1848,7 +1854,7 @@ function list_getProfileEditCount($memID)
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $start
  * @param int $items_per_page
  * @param string $sort
@@ -1936,7 +1942,7 @@ function list_getProfileEdits($start, $items_per_page, $sort, $memID)
 
 /**
  * @todo needs a description
- * 
+ *
  * @param int $memID id_member
  */
 function showPermissions($memID)
@@ -2118,7 +2124,7 @@ function showPermissions($memID)
 
 /**
  * View a members warnings?
- * 
+ *
  * @param int $memID id_member
  */
 function viewWarning($memID)

+ 14 - 4
Sources/Profile.php

@@ -9,7 +9,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -30,7 +30,7 @@ function ModifyProfile($post_errors = array())
 
 	// Don't reload this as we may have processed error strings.
 	if (empty($post_errors))
-		loadLanguage('Profile');
+		loadLanguage('Profile+Drafts');
 	loadTemplate('Profile');
 
 	require_once($sourcedir . '/Subs-Menu.php');
@@ -116,6 +116,16 @@ function ModifyProfile($post_errors = array())
 						'any' => 'profile_view_any',
 					),
 				),
+				'showdrafts' => array(
+					'label' => $txt['drafts_show'],
+					'file' => 'Drafts.php',
+					'function' => 'showProfileDrafts',
+					'enabled' => !empty($modSettings['drafts_enabled']) && $context['user']['is_owner'],
+					'permission' => array(
+						'own' => 'profile_view_own',
+						'any' =>  array(),
+					),
+				),
 				'permissions' => array(
 					'label' => $txt['showPermissions'],
 					'file' => 'Profile-View.php',
@@ -449,12 +459,12 @@ function ModifyProfile($post_errors = array())
 	unset($profile_areas);
 
 	// Now the context is setup have we got any security checks to carry out additional to that above?
+	if (isset($security_checks['validateToken']))
+		validateToken($token_name, $token_type);
 	if (isset($security_checks['session']))
 		checkSession($security_checks['session']);
 	if (isset($security_checks['validate']))
 		validateSession();
-	if (isset($security_checks['validateToken']))
-		validateToken($token_name, $token_type);
 	if (isset($security_checks['permission']))
 		isAllowedTo($security_checks['permission']);
 

+ 3 - 3
Sources/QueryString.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -308,7 +308,7 @@ function isValidIPv6($ip)
 /**
  * Converts IPv6s to numbers.  This makes ban checks much easier.
  * @param string $ip ip address to be converted
- * @return array 
+ * @return array
  */
 function convertIPv6toInts($ip)
 {
@@ -373,7 +373,7 @@ function expandIPv6($addr, $strict_check = true)
 	// Save this incase of repeated use.
 	$converted[$addr] = $result;
 
-	// Quick check to make sure the length is as expected. 
+	// Quick check to make sure the length is as expected.
 	if (!$strict_check || strlen($result) == 39)
 		return $result;
 	else

+ 7 - 7
Sources/Recent.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -432,7 +432,7 @@ function UnreadTopics()
 		header('HTTP/1.1 403 Forbidden');
 		die;
 	}
-	
+
 	$context['showCheckboxes'] = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read'];
 
 	$context['showing_all_topics'] = isset($_GET['all']);
@@ -1233,7 +1233,7 @@ function UnreadTopics()
 					'name' => $row['first_poster_name'],
 					'id' => $row['id_first_member'],
 					'href' => $scripturl . '?action=profile;u=' . $row['id_first_member'],
-					'link' => !empty($row['id_first_member']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_first_member'] . '" title="' . $txt['profile_of'] . ' ' . $row['first_poster_name'] . '">' . $row['first_poster_name'] . '</a>' : $row['first_poster_name']
+					'link' => !empty($row['id_first_member']) ? '<a class="preview" href="' . $scripturl . '?action=profile;u=' . $row['id_first_member'] . '" title="' . $txt['profile_of'] . ' ' . $row['first_poster_name'] . '">' . $row['first_poster_name'] . '</a>' : $row['first_poster_name']
 				),
 				'time' => timeformat($row['first_poster_time']),
 				'timestamp' => forum_time(true, $row['first_poster_time']),
@@ -1318,7 +1318,7 @@ function UnreadTopics()
 
 	$context['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']);
 	$context['topics_to_mark'] = implode('-', $topic_ids);
-	
+
 	if ($settings['show_mark_read'])
 	{
 		// Build the recent button array.
@@ -1335,9 +1335,9 @@ function UnreadTopics()
 					'lang' => true,
 					'url' => 'javascript:document.quickModForm.submit();',
 				);
-				
+
 			if (!empty($context['topics']) && !$context['showing_all_topics'])
-				$context['recent_buttons']['readall'] = array('text' => 'unread_topics_all', 'image' => 'markreadall.png', 'lang' => true, 'url' => $scripturl . '?action=unread;all' . $context['querystring_board_limits'], 'active' => true);	
+				$context['recent_buttons']['readall'] = array('text' => 'unread_topics_all', 'image' => 'markreadall.png', 'lang' => true, 'url' => $scripturl . '?action=unread;all' . $context['querystring_board_limits'], 'active' => true);
 		}
 		elseif (!$is_topics && isset($context['topics_to_mark']))
 		{
@@ -1357,7 +1357,7 @@ function UnreadTopics()
 		// Allow mods to add additional buttons here
 		call_integration_hook('integrate_recent_buttons');
 	}
-	
+
 	// Allow helpdesks and bug trackers and what not to add their own unread data (just add a template_layer to show custom stuff in the template!)
  	call_integration_hook('integrate_unread_list');
 }

+ 11 - 19
Sources/Register.php

@@ -9,7 +9,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -105,7 +105,7 @@ function Register($reg_errors = array())
 			$context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.txt'), true, 'agreement');
 		else
 			$context['agreement'] = '';
-		
+
 		// Nothing to show, lets disable registration and inform the admin of this error
 		if (empty($context['agreement']))
 		{
@@ -522,7 +522,11 @@ function Register2($verifiedOpenID = false)
  */
 function Activate()
 {
-	global $context, $txt, $modSettings, $scripturl, $sourcedir, $smcFunc, $language;
+	global $context, $txt, $modSettings, $scripturl, $sourcedir, $smcFunc, $language, $user_info;
+
+	// Logged in users should not bother to activate their accounts
+	if (!empty($user_info['id']))
+		redirectexit();
 
 	loadLanguage('Login');
 	loadTemplate('Login');
@@ -832,26 +836,14 @@ function RegisterCheckUsername()
 	// This is XML!
 	loadTemplate('Xml');
 	$context['sub_template'] = 'check_username';
-	$context['checked_username'] = isset($_GET['username']) ? $_GET['username'] : '';
+	$context['checked_username'] = isset($_GET['username']) ? un_htmlspecialchars($_GET['username']) : '';
 	$context['valid_username'] = true;
 
 	// Clean it up like mother would.
 	$context['checked_username'] = preg_replace('~[\t\n\r\x0B\0' . ($context['utf8'] ? '\x{A0}' : '\xA0') . ']+~' . ($context['utf8'] ? 'u' : ''), ' ', $context['checked_username']);
-	if ($smcFunc['strlen']($context['checked_username']) > 25)
-		$context['checked_username'] = $smcFunc['htmltrim']($smcFunc['substr']($context['checked_username'], 0, 25));
-
-	// Only these characters are permitted.
-	if (preg_match('~[<>&"\'=\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $context['checked_username'])) != 0 || $context['checked_username'] == '_' || $context['checked_username'] == '|' || strpos($context['checked_username'], '[code') !== false || strpos($context['checked_username'], '[/code') !== false)
-		$context['valid_username'] = false;
 
-	if (stristr($context['checked_username'], $txt['guest_title']) !== false)
-		$context['valid_username'] = false;
+	require_once($sourcedir . '/Subs-Auth.php');
+	$errors = validateUsername(0, $context['checked_username'], true);
 
-	if (trim($context['checked_username']) == '')
-		$context['valid_username'] = false;
-	else
-	{
-		require_once($sourcedir . '/Subs-Members.php');
-		$context['valid_username'] &= isReservedName($context['checked_username'], 0, false, false) ? 0 : 1;
-	}
+	$context['valid_username'] = empty($errors);
 }

+ 2 - 2
Sources/Reminder.php

@@ -6,7 +6,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -70,7 +70,7 @@ function RemindPick()
 	// You must enter a username/email address.
 	if (empty($where))
 		fatal_lang_error('username_no_exist', false);
-		
+
 	// Make sure we are not being slammed
 	spamProtection('remind');
 

+ 44 - 24
Sources/RemoveTopic.php

@@ -1,11 +1,14 @@
 <?php
 
 /**
+ * The contents of this file handle the deletion of topics, posts, and related
+ * paraphernalia.
+ * 
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.0
@@ -17,23 +20,12 @@ if (!defined('SMF'))
 /*	The contents of this file handle the deletion of topics, posts, and related
 	paraphernalia.  It has the following functions:
 
-	void RemoveTopic2()
-		// !!!
-
-	void DeleteMessage()
-		// !!!
-
-	void RemoveOldTopics2()
-		// !!!
-
-	void removeTopics(array topics, bool decreasePostCount = true, bool ignoreRecycling = false)
-		// !!!
-
-	bool removeMessage(int id_msg, bool decreasePostCount = true)
-		// !!!
 */
 
-// Completely remove an entire topic.
+/**
+ * Completely remove an entire topic.
+ * Redirects to the board when completed.
+ */
 function RemoveTopic2()
 {
 	global $user_info, $topic, $board, $sourcedir, $smcFunc, $context, $modSettings;
@@ -84,7 +76,10 @@ function RemoveTopic2()
 	redirectexit('board=' . $board . '.0');
 }
 
-// Remove just a single post.
+/**
+ * Remove just a single post.
+ * On completion redirect to the topic or to the board.
+ */
 function DeleteMessage()
 {
 	global $user_info, $topic, $board, $modSettings, $smcFunc;
@@ -151,7 +146,10 @@ function DeleteMessage()
 		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
 }
 
-// So long as you are sure... all old posts will be gone.
+/**
+ * So long as you are sure... all old posts will be gone.
+ * Used in ManageMaintenance.php to prune old topics.
+ */
 function RemoveOldTopics2()
 {
 	global $modSettings, $smcFunc;
@@ -221,7 +219,13 @@ function RemoveOldTopics2()
 	redirectexit('action=admin;area=maintain;sa=topics;done=purgeold');
 }
 
-// Removes the passed id_topic's. (permissions are NOT checked here!)
+/**
+ * Removes the passed id_topic's. (permissions are NOT checked here!).
+ *
+ * @param array/int $topics The topics to remove (can be an id or an array of ids).
+ * @param bool $decreasePostCount if true users' post count will be reduced
+ * @param bool $ignoreRecycling if true topics are not moved to the recycle board (if it exists).
+ */
 function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false)
 {
 	global $sourcedir, $modSettings, $smcFunc;
@@ -232,7 +236,7 @@ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = fal
 	// Only a single topic.
 	if (is_numeric($topics))
 		$topics = array($topics);
-		
+
 	// Decrease the post counts.
 	if ($decreasePostCount)
 	{
@@ -533,7 +537,7 @@ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = fal
 			'topics' => $topics,
 		)
 	);
-	
+
 	// Maybe there's a mod that wants to delete topic related data of its own
  	call_integration_hook('integrate_remove_topics', array($topics));
 
@@ -551,7 +555,15 @@ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = fal
 	updateLastMessages($updates);
 }
 
-// Remove a specific message (including permission checks).
+/**
+ * Remove a specific message (including permission checks).
+ * - normally, local and global should be the localCookies and globalCookies settings, respectively.
+ * - uses boardurl to determine these two things.
+ *
+ * @param int $message The message id
+ * @param bool $decreasePostCount if true users' post count will be reduced
+ * @return array an array to set the cookie on with domain and path in it, in that order
+ */
 function removeMessage($message, $decreasePostCount = true)
 {
 	global $board, $sourcedir, $modSettings, $user_info, $smcFunc, $context;
@@ -962,7 +974,7 @@ function removeMessage($message, $decreasePostCount = true)
 			'id_msg' => $message,
 		);
 		removeAttachments($attachmentQuery);
-		
+
 		// Allow mods to remove message related data of their own (likes, maybe?)
 		call_integration_hook('integrate_remove_message', array($message));
 	}
@@ -984,6 +996,9 @@ function removeMessage($message, $decreasePostCount = true)
 	return false;
 }
 
+/**
+ * Move back a topic from the recycle board to its original board.
+ */
 function RestoreTopic()
 {
 	global $context, $smcFunc, $modSettings, $sourcedir;
@@ -1215,7 +1230,9 @@ function RestoreTopic()
 	redirectexit();
 }
 
-// Take a load of messages from one place and stick them in a topic.
+/**
+ * Take a load of messages from one place and stick them in a topic.
+ */
 function mergePosts($msgs = array(), $from_topic, $target_topic)
 {
 	global $context, $smcFunc, $modSettings, $sourcedir;
@@ -1468,6 +1485,9 @@ function mergePosts($msgs = array(), $from_topic, $target_topic)
 	updateLastMessages(array($from_board, $target_board));
 }
 
+/**
+ * Try to determine if the topic has already been deleted by another user.
+ */
 function removeDeleteConcurrence()
 {
 	global $modSettings, $board, $topic, $smcFunc, $scripturl, $context;

+ 1 - 1
Sources/RepairBoards.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 13 - 13
Sources/Reports.php

@@ -6,16 +6,16 @@
  * core report generation is done in two areas. Firstly, a report "generator"
  * will fill context with relevant data. Secondly, the choice of sub-template
  * will determine how this data is shown to the user
- * 
+ *
  * Functions ending with "Report" are responsible for generating data for reporting.
  * They are all called from ReportsMain.
  * Never access the context directly, but use the data handling functions to do so.
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -103,16 +103,16 @@ function ReportsMain()
 
 	// Make the page title more descriptive.
 	$context['page_title'] .= ' - ' . (isset($txt['gr_type_' . $context['report_type']]) ? $txt['gr_type_' . $context['report_type']] : $context['report_type']);
-	
+
 	// Build the reports button array.
 	$context['report_buttons'] = array(
 		'generate_reports' => array('text' => 'generate_reports', 'image' => 'print.png', 'lang' => true, 'url' => $scripturl . '?action=admin;area=reports', 'active' => true),
 		'print' => array('text' => 'print', 'image' => 'print.png', 'lang' => true, 'url' => $scripturl . '?action=admin;area=reports;rt=' . $context['report_type']. ';st=print', 'custom' => 'target="_blank"'),
 	);
-	
+
 	// Allow mods to add additional buttons here
-	call_integration_hook('integrate_report_buttons');	
-	
+	call_integration_hook('integrate_report_buttons');
+
 	// Now generate the data.
 	$context['report_types'][$context['report_type']]['function']();
 
@@ -785,7 +785,7 @@ function StaffReport()
  * context, ready for filling using addData().
  * Fills the context variable current_table with the ID of the table created.
  * Keeps track of the current table count using context variable table_count.
- * 
+ *
  * @param string $title = '' Title to be displayed with this data table.
  * @param string $default_value = '' Value to be displayed if a key is missing from a row.
  * @param string $shading = 'all' Should the left, top or both (all) parts of the table beshaded?
@@ -839,7 +839,7 @@ function newTable($title = '', $default_value = '', $shading = 'all', $width_nor
  * if any key in the incoming data begins with '#sep#', the function
  * will add a separator accross the table at this point.
  * once the incoming data has been sanitized, it is added to the table.
- * 
+ *
  * @param array $inc_data
  * @param int $custom_table = null
  */
@@ -902,10 +902,10 @@ function addData($inc_data, $custom_table = null)
 
 /**
  * Add a separator row, only really used when adding data by rows.
- * 
+ *
  * @param string $title = ''
  * @param string $custom_table = null
- * 
+ *
  * @return bool returns false if there are no tables
  */
 function addSeparator($title = '', $custom_table = null)
@@ -966,7 +966,7 @@ function finishTables()
 
 /**
  * Set the keys in use by the tables - these ensure entries MUST exist if the data isn't sent.
- * 
+ *
  * sets the current set of "keys" expected in each data array passed to
  * addData. It also sets the way we are adding data to the data table.
  * method specifies whether the data passed to addData represents a new
@@ -975,7 +975,7 @@ function finishTables()
  * addData().
  * if reverse is set to true, then the values of the variable "keys"
  * are used as oppossed to the keys(!
- * 
+ *
  * @param string $method = 'rows' rows or cols
  * @param array $keys = array()
  * @param bool $reverse = false

+ 50 - 8
Sources/ScheduledTasks.php

@@ -2,12 +2,12 @@
 
 /**
  * This file is automatically called and handles all manner of scheduled things.
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -28,6 +28,8 @@ function AutoTask()
 		ReduceMailQueue();
 	else
 	{
+		call_integration_hook('integrate_autotask_include');
+
 		// Select the next task to do.
 		$request = $smcFunc['db_query']('', '
 			SELECT id_task, task, next_time, time_offset, time_regularity, time_unit
@@ -1685,16 +1687,16 @@ function scheduled_remove_temp_attachments()
 /**
  * Check for move topic notices that have past their best by date
  */
-function scheduled_remove_topic_redirect() 
+function scheduled_remove_topic_redirect()
 {
 	global $smcFunc, $sourcedir;
-	
+
 	// init
 	$topics = array();
-	
+
 	// We will need this for lanaguage files
 	loadEssentialThemeData();
-	
+
 	// Find all of the old MOVE topic notices that were set to expire
 	$request = $smcFunc['db_query']('', '
 		SELECT id_topic
@@ -1705,11 +1707,11 @@ function scheduled_remove_topic_redirect()
 			'redirect_expires' => time(),
 		)
 	);
-	
+
 	while ($row = $smcFunc['db_fetch_row']($request))
 		$topics[] = $row[0];
 	$smcFunc['db_free_result']($request);
-	
+
 	// Zap, your gone
 	if (count($topics) > 0)
 	{
@@ -1719,3 +1721,43 @@ function scheduled_remove_topic_redirect()
 
 	return true;
 }
+
+/**
+ * Check for old drafts and remove them
+ */
+function scheduled_remove_old_drafts()
+{
+	global $smcFunc, $sourcedir, $modSettings;
+
+	if (empty($modSettings['drafts_keep_days']))
+		return true;
+
+	// init
+	$drafts= array();
+
+	// We need this for lanaguage items
+	loadEssentialThemeData();
+
+	// Find all of the old drafts
+	$request = $smcFunc['db_query']('', '
+		SELECT id_draft
+		FROM {db_prefix}user_drafts
+		WHERE poster_time <= {int:poster_time_old}',
+		array(
+			'poster_time_old' => time() - (86400 * $modSettings['drafts_keep_days']),
+		)
+	);
+
+	while ($row = $smcFunc['db_fetch_row']($request))
+		$drafts[] = (int) $row[0];
+	$smcFunc['db_free_result']($request);
+
+	// If we have old one, remove them
+	if (count($drafts) > 0)
+	{
+		require_once($sourcedir . '/Drafts.php');
+		DeleteDraft($drafts, false);
+	}
+
+	return true;
+}

+ 54 - 46
Sources/Search.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -267,15 +267,32 @@ function PlushSearch2()
 	}
 
 	$weight_factors = array(
-		'frequency' => 'COUNT(*) / (MAX(t.num_replies) + 1)',
-		'age' => 'CASE WHEN MAX(m.id_msg) < {int:min_msg} THEN 0 ELSE (MAX(m.id_msg) - {int:min_msg}) / {int:recent_message} END',
-		'length' => 'CASE WHEN MAX(t.num_replies) < {int:huge_topic_posts} THEN MAX(t.num_replies) / {int:huge_topic_posts} ELSE 1 END',
-		'subject' => '0',
-		'first_message' => 'CASE WHEN MIN(m.id_msg) = MAX(t.id_first_msg) THEN 1 ELSE 0 END',
-		'sticky' => 'MAX(t.is_sticky)',
+		'frequency' => array(
+			'search' => 'COUNT(*) / (MAX(t.num_replies) + 1)',
+			'results' => '(t.num_replies + 1)',
+		),
+		'age' => array(
+			'search' => 'CASE WHEN MAX(m.id_msg) < {int:min_msg} THEN 0 ELSE (MAX(m.id_msg) - {int:min_msg}) / {int:recent_message} END',
+			'results' => 'CASE WHEN t.id_first_msg < {int:min_msg} THEN 0 ELSE (t.id_first_msg - {int:min_msg}) / {int:recent_message} END',
+		),
+		'length' => array(
+			'search' => 'CASE WHEN MAX(t.num_replies) < {int:huge_topic_posts} THEN MAX(t.num_replies) / {int:huge_topic_posts} ELSE 1 END',
+			'results' => 'CASE WHEN t.num_replies < {int:huge_topic_posts} THEN t.num_replies / {int:huge_topic_posts} ELSE 1 END',
+		),
+		'subject' => array(
+			'search' => 0,
+			'results' => 0,
+		),
+		'first_message' => array(
+			'search' => 'CASE WHEN MIN(m.id_msg) = MAX(t.id_first_msg) THEN 1 ELSE 0 END',
+		),
+		'sticky' => array(
+			'search' => 'MAX(t.is_sticky)',
+			'results' => 't.is_sticky',
+		),
 	);
 
-	call_integration_hook('integrate_search_weights', array($weight_factors));
+	call_integration_hook('integrate_search_weights', array(&$weight_factors));
 
 	$weight = array();
 	$weight_total = 0;
@@ -574,7 +591,7 @@ function PlushSearch2()
 		'num_replies',
 		'id_msg',
 	);
-	call_integration_hook('integrate_search_sort_columns', array($sort_columns));
+	call_integration_hook('integrate_search_sort_columns', array(&$sort_columns));
 	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
 		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
 	$search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'relevance';
@@ -589,7 +606,7 @@ function PlushSearch2()
 	$recentMsg = $modSettings['maxMsgID'] - $minMsg;
 
 	// *** Parse the search query
-	call_integration_hook('integrate_search_params', array($search_params));
+	call_integration_hook('integrate_search_params', array(&$search_params));
 
 	/*
 	 * Unfortunately, searching for words like this is going to be slow, so we're blacklisting them.
@@ -598,7 +615,7 @@ function PlushSearch2()
 	 * @todo Maybe only blacklist if they are the only word, or "any" is used?
 	 */
 	$blacklisted_words = array('img', 'url', 'quote', 'www', 'http', 'the', 'is', 'it', 'are', 'if');
-	call_integration_hook('integrate_search_blacklisted_words', array($blacklisted_words));
+	call_integration_hook('integrate_search_blacklisted_words', array(&$blacklisted_words));
 
 	// What are we searching for?
 	if (empty($search_params['search']))
@@ -1071,16 +1088,15 @@ function PlushSearch2()
 							$subject_query_params['excluded_phrases_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
 						}
 					}
-					call_integration_hook('integrate_subject_only_search_query', array($subject_query, $subject_query_params));
+					call_integration_hook('integrate_subject_only_search_query', array(&$subject_query, &$subject_query_params));
 
 					$relevance = '1000 * (';
 					foreach ($weight_factors as $type => $value)
 					{
 						$relevance .= $weight[$type];
-						if (!empty($value))
-							$relevance .= ' * ' . $value;
-						$relevance .= ' +
-							';
+						if (!empty($value['search']))
+							$relevance .= ' * ' . $value['search'];
+						$relevance .= ' + ';
 					}
 					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
 
@@ -1187,8 +1203,12 @@ function PlushSearch2()
 					$main_query['select']['num_matches'] = '1 AS num_matches';
 
 					$main_query['weights'] = array(
-						'age' => '((m.id_msg - t.id_first_msg) / CASE WHEN t.id_last_msg = t.id_first_msg THEN 1 ELSE t.id_last_msg - t.id_first_msg END)',
-						'first_message' => 'CASE WHEN m.id_msg = t.id_first_msg THEN 1 ELSE 0 END',
+						'age' => array(
+							'search' => '((m.id_msg - t.id_first_msg) / CASE WHEN t.id_last_msg = t.id_first_msg THEN 1 ELSE t.id_last_msg - t.id_first_msg END)',
+						),
+						'first_message' => array(
+							'search' => 'CASE WHEN m.id_msg = t.id_first_msg THEN 1 ELSE 0 END',
+						),
 					);
 
 					if (!empty($search_params['topic']))
@@ -1316,7 +1336,7 @@ function PlushSearch2()
 								$subject_query['params']['exclude_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
 							}
 						}
-						call_integration_hook('integrate_subject_search_query', array($subject_query));
+						call_integration_hook('integrate_subject_search_query', array(&$subject_query));
 
 						// Nothing to search for?
 						if (empty($subject_query['where']))
@@ -1371,7 +1391,7 @@ function PlushSearch2()
 
 					if ($numSubjectResults !== 0)
 					{
-						$main_query['weights']['subject'] = 'CASE WHEN MAX(lst.id_topic) IS NULL THEN 0 ELSE 1 END';
+						$main_query['weights']['subject']['search'] = 'CASE WHEN MAX(lst.id_topic) IS NULL THEN 0 ELSE 1 END';
 						$main_query['left_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (' . ($createTemporary ? '' : 'lst.id_search = {int:id_search} AND ') . 'lst.id_topic = t.id_topic)';
 						if (!$createTemporary)
 							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
@@ -1534,7 +1554,7 @@ function PlushSearch2()
 						$main_query['parameters']['board_query'] = $boardQuery;
 					}
 				}
-				call_integration_hook('integrate_main_search_query', array($main_query));
+				call_integration_hook('integrate_main_search_query', array(&$main_query));
 
 				// Did we either get some indexed results, or otherwise did not do an indexed query?
 				if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params))
@@ -1544,8 +1564,8 @@ function PlushSearch2()
 					foreach ($main_query['weights'] as $type => $value)
 					{
 						$relevance .= $weight[$type];
-						if (!empty($value))
-							$relevance .= ' * ' . $value;
+						if (!empty($value['search']))
+							$relevance .= ' * ' . $value['search'];
 						$relevance .= ' + ';
 						$new_weight_total += $weight[$type];
 					}
@@ -1608,14 +1628,14 @@ function PlushSearch2()
 				if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0)
 				{
 					$relevance = '1000 * (';
-					foreach ($main_query['weights'] as $type => $value)
-					{
-						$relevance .= $weight[$type];
-						if (!empty($value))
-							$relevance .= ' * ' . $value;
-						$relevance .= ' +
-							';
-					}
+					foreach ($weight_factors as $type => $value)
+						if (isset($value['results']))
+						{
+							$relevance .= $weight[$type];
+							if (!empty($value['results']))
+								$relevance .= ' * ' . $value['results'];
+							$relevance .= ' + ';
+						}
 					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
 
 					$usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts));
@@ -1625,12 +1645,12 @@ function PlushSearch2()
 						SELECT
 							{int:id_search},
 							t.id_topic,
-							' . $relevance. ',
+							' . $relevance . ',
 							t.id_first_msg,
 							1
 						FROM {db_prefix}topics AS t
 							INNER JOIN {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (lst.id_topic = t.id_topic)'
-						. ($createTemporary ? '' : 'WHERE lst.id_search = {int:id_search}')
+						. ($createTemporary ? '' : ' WHERE lst.id_search = {int:id_search}')
 						. (empty($modSettings['search_max_results']) ? '' : '
 						LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])),
 						array(
@@ -2056,18 +2076,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');
 	}
@@ -2106,7 +2114,7 @@ function prepareSearchContext($reset = false)
 	);
 	$counter++;
 
-	call_integration_hook('integrate_search_message_context', array($counter, $output));
+	call_integration_hook('integrate_search_message_context', array($counter, &$output));
 
 	return $output;
 }

+ 6 - 6
Sources/SearchAPI-Custom.php

@@ -5,7 +5,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -210,7 +210,7 @@ class custom_search
 
 		return $ignoreRequest;
 	}
-	
+
 	/**
 	 * After a post is made, we update the search index database.
 	 */
@@ -239,21 +239,21 @@ class custom_search
 	public function postModified($msgOptions, $topicOptions, $posterOptions)
 	{
 		global $modSettings, $smcFunc;
-		
+
 		if (isset($msgOptions['body']))
 		{
 			$customIndexSettings = unserialize($modSettings['search_custom_index_config']);
 			$stopwords = empty($modSettings['search_stopwords']) ? array() : explode(',', $modSettings['search_stopwords']);
 			$old_body = isset($msgOptions['old_body']) ? $msgOptions['old_body'] : '';
-			
+
 			// create thew new and old index
 			$old_index = text2words($old_body, $customIndexSettings['bytes_per_word'], true);
 			$new_index = text2words($msgOptions['body'], $customIndexSettings['bytes_per_word'], true);
-			
+
 			// Calculate the words to be added and removed from the index.
 			$removed_words = array_diff(array_diff($old_index, $new_index), $stopwords);
 			$inserted_words = array_diff(array_diff($new_index, $old_index), $stopwords);
-			
+
 			// Delete the removed words AND the added ones to avoid key constraints.
 			if (!empty($removed_words))
 			{

+ 54 - 59
Sources/SearchAPI-Fulltext.php

@@ -5,7 +5,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -22,9 +22,6 @@ class fulltext_search
 	public $min_smf_version = 'SMF 2.1 Alpha 1';
 	// Is it supported?
 	public $is_supported = true;
-
-	// Can we do a boolean search - tested on construct.
-	protected $canDoBooleanSearch = false;
 	// What words are banned?
 	protected $bannedWords = array();
 	// What is the minimum word length?
@@ -32,6 +29,10 @@ class fulltext_search
 	// What databases support the fulltext index?
 	protected $supported_databases = array('mysql');
 
+	/**
+	 * fulltext_search::__construct()
+	 *
+	 */
 	public function __construct()
 	{
 		global $smcFunc, $db_connection, $modSettings, $db_type;
@@ -43,14 +44,19 @@ class fulltext_search
 			return;
 		}
 
-		// Some MySQL versions are superior to others :P.
-		$this->canDoBooleanSearch = version_compare($smcFunc['db_server_info']($db_connection), '4.0.1', '>=');
-
 		$this->bannedWords = empty($modSettings['search_banned_words']) ? array() : explode(',', $modSettings['search_banned_words']);
 		$this->min_word_length = $this->_getMinWordLength();
 	}
 
-	// Check whether the method can be performed by this API.
+	/**
+	 * fulltext_search::supportsMethod()
+	 *
+	 * Check whether the method can be performed by this API.
+	 *
+	 * @param mixed $methodName
+	 * @param mixed $query_params
+	 * @return
+	 */
 	public function supportsMethod($methodName, $query_params = null)
 	{
 		switch ($methodName)
@@ -68,7 +74,13 @@ class fulltext_search
 		}
 	}
 
-	// What is the minimum word length full text supports?
+	/**
+	 * fulltext_search::_getMinWordLength()
+	 *
+	 * What is the minimum word length full text supports?
+	 *
+	 * @return
+	 */
 	protected function _getMinWordLength()
 	{
 		global $smcFunc;
@@ -92,7 +104,7 @@ class fulltext_search
 
 		return $min_word_length;
 	}
-
+	
 	/**
 	 * callback function for usort used to sort the fulltext results.
 	 * the order of sorting is: large words, small words, large words that
@@ -103,26 +115,34 @@ class fulltext_search
 	 */
 	public function searchSort($a, $b)
 	{
-		global $modSettings, $excludedWords;
-
-		$x = strlen($a) - (in_array($a, $excludedWords) ? 1000 : 0);
-		$y = strlen($b) - (in_array($b, $excludedWords) ? 1000 : 0);
+		global $modSettings, $excludedWords, $smcFunc;
 
+		$x = $smcFunc['strlen']($a) - (in_array($a, $excludedWords) ? 1000 : 0);
+		$y = $smcFunc['strlen']($b) - (in_array($b, $excludedWords) ? 1000 : 0);
+		
 		return $x < $y ? 1 : ($x > $y ? -1 : 0);
 	}
-
-	// Do we have to do some work with the words we are searching for to prepare them?
+	
+	/**
+	 * fulltext_search::prepareIndexes()
+	 *
+	 * Do we have to do some work with the words we are searching for to prepare them?
+	 *
+	 * @param mixed $word
+	 * @param mixed $wordsSearch
+	 * @param mixed $wordsExclude
+	 * @param mixed $isExcluded
+	 * @return
+	 */
 	public function prepareIndexes($word, &$wordsSearch, &$wordsExclude, $isExcluded)
 	{
 		global $modSettings, $smcFunc;
 
 		$subwords = text2words($word, null, false);
 
-		if (!$this->canDoBooleanSearch && count($subwords) > 1 && empty($modSettings['search_force_index']))
-			$wordsSearch['words'][] = $word;
-		elseif (empty($modSettings['search_force_index']) && $this->canDoBooleanSearch)
+		if (empty($modSettings['search_force_index']))
 		{
-			// A boolean capable search engine and not forced to only use an index, we may use a non index search
+			// A boolean capable search engine and not forced to only use an index, we may use a non indexed search
 			// this is harder on the server so we are restrictive here
 			if (count($subwords) > 1 && preg_match('~[.:@$]~', $word))
 			{
@@ -141,37 +161,21 @@ class fulltext_search
 			}
 		}
 
-		if ($this->canDoBooleanSearch)
-		{
-			$fulltextWord = count($subwords) === 1 ? $word : '"' . $word . '"';
-			$wordsSearch['indexed_words'][] = $fulltextWord;
-			if ($isExcluded)
-				$wordsExclude[] = $fulltextWord;
-		}
-		// Excluded phrases don't benefit from being split into subwords.
-		elseif (count($subwords) > 1 && $isExcluded)
-			return;
-		else
-		{
-			$relyOnIndex = true;
-			foreach ($subwords as $subword)
-			{
-				if (($smcFunc['strlen']($subword) >= $this->min_word_length) && !in_array($subword, $this->bannedWords))
-				{
-					$wordsSearch['indexed_words'][] = $subword;
-					if ($isExcluded)
-						$wordsExclude[] = $subword;
-				}
-				elseif (!in_array($subword, $this->bannedWords))
-					$relyOnIndex = false;
-			}
-
-			if ($this->canDoBooleanSearch && !$relyOnIndex && empty($modSettings['search_force_index']))
-				$wordsSearch['words'][] = $word;
-		}
+		$fulltextWord = count($subwords) === 1 ? $word : '"' . $word . '"';
+		$wordsSearch['indexed_words'][] = $fulltextWord;
+		if ($isExcluded)
+			$wordsExclude[] = $fulltextWord;
 	}
 
-	// Search for indexed words.
+	/**
+	 * fulltext_search::indexedWordQuery()
+	 *
+	 * Search for indexed words.
+	 *
+	 * @param mixed $words
+	 * @param mixed $search_data
+	 * @return
+	 */
 	public function indexedWordQuery($words, $search_data)
 	{
 		global $modSettings, $smcFunc;
@@ -225,7 +229,7 @@ class fulltext_search
 			$query_where[] = 'MATCH (body) AGAINST ({string:body_match})';
 			$query_params['body_match'] = implode(' ', array_diff($words['indexed_words'], $query_params['excluded_index_words']));
 		}
-		elseif ($this->canDoBooleanSearch)
+		else
 		{
 			$query_params['boolean_match'] = '';
 
@@ -240,15 +244,6 @@ class fulltext_search
 			if ($query_params['boolean_match'])
 				$query_where[] = 'MATCH (body) AGAINST ({string:boolean_match} IN BOOLEAN MODE)';
 		}
-		else
-		{
-			$count = 0;
-			foreach ($words['indexed_words'] as $fulltextWord)
-			{
-				$query_where[] = (in_array($fulltextWord, $query_params['excluded_index_words']) ? 'NOT ' : '') . 'MATCH (body) AGAINST ({string:fulltext_match_' . $count . '})';
-				$query_params['fulltext_match_' . $count++] = $fulltextWord;
-			}
-		}
 
 		$ignoreRequest = $smcFunc['db_search_query']('insert_into_log_messages_fulltext', ($smcFunc['db_support_ignore'] ? ( '
 			INSERT IGNORE INTO {db_prefix}' . $search_data['insert_into'] . '

+ 1 - 1
Sources/SearchAPI-Standard.php

@@ -5,7 +5,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 25 - 14
Sources/Security.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -22,6 +22,7 @@ if (!defined('SMF'))
  * Makes sure the user is who they claim to be by requiring a password to be typed in every hour.
  * Is turned on and off by the securityDisable setting.
  * Uses the adminLogin() function of Subs-Auth.php if they need to login, which saves all request (post and get) data.
+ *
  * @param string $type = admin
  */
 function validateSession($type = 'admin')
@@ -97,6 +98,7 @@ function validateSession($type = 'admin')
  * Require a user who is logged in. (not a guest.)
  * Checks if the user is currently a guest, and if so asks them to login with a message telling them why.
  * Message is what to tell them when asking them to login.
+ *
  * @param string $message = ''
  */
 function is_not_guest($message = '')
@@ -162,6 +164,7 @@ function is_not_guest($message = '')
  * Checks if the user is banned, and if so dies with an error.
  * Caches this information for optimization purposes.
  * Forces a recheck if force_check is true.
+ *
  * @param bool $forceCheck = false
  */
 function is_not_banned($forceCheck = false)
@@ -491,6 +494,7 @@ function banPermissions()
  * Log a ban in the database.
  * Log the current user in the ban logs.
  * Increment the hit counters for the specified ban ID's (if any.)
+ *
  * @param array $ban_ids = array()
  * @param string $email = null
  */
@@ -525,6 +529,7 @@ function log_ban($ban_ids = array(), $email = null)
  * Checks if a given email address might be banned.
  * Check if a given email is banned.
  * Performs an immediate ban if the turns turns out positive.
+ *
  * @param string $email
  * @param string $restriction
  * @param string $error
@@ -594,6 +599,7 @@ function isBannedEmail($email, $restriction, $error)
  * Depends on the disableCheckUA setting, which is usually missing.
  * Will check GET, POST, or REQUEST depending on the passed type.
  * Also optionally checks the referring action if passed. (note that the referring action must be by GET.)
+ *
  * @param string $type = 'post' (post, get, request)
  * @param string $from_action = ''
  * @param bool $is_fatal = true
@@ -707,6 +713,7 @@ function checkSession($type = 'post', $from_action = '', $is_fatal = true)
 
 /**
  * Check if a specific confirm parameter was given.
+ *
  * @param string $action
  */
 function checkConfirm($action)
@@ -727,6 +734,7 @@ function checkConfirm($action)
 
 /**
  * Lets give you a token of our appreciation.
+ *
  * @param string $action
  * @param string $type = 'post'
  * @return array
@@ -756,7 +764,7 @@ function createToken($action, $type = 'post')
  */
 function validateToken($action, $type = 'post', $reset = true)
 {
-	global $modSettings, $sourcedir;
+	global $modSettings;
 
 	$type = $type == 'get' || $type == 'request' ? $type : 'post';
 
@@ -798,13 +806,6 @@ function validateToken($action, $type = 'post', $reset = true)
 		// I'm back baby.
 		createToken($action, $type);
 
-		// Need to type in a password for that, man.
-		if (!isset($_GET['xml']))
-		{
-			require_once($sourcedir . '/Subs-Auth.php');
-			adminLogin($type, $action);
-		}
-
 		fatal_lang_error('token_verify_fail', false);
 	}
 	// Remove this token as its useless
@@ -819,7 +820,10 @@ function validateToken($action, $type = 'post', $reset = true)
 }
 
 /**
- * Clean up a little.
+ * Removes old unused tokens from session
+ * defaults to 3 hours before a token is considered expired
+ * if $complete = true will remove all tokens
+ *
  * @param bool $complete = false
  */
 function cleanTokens($complete = false)
@@ -887,6 +891,7 @@ function checkSubmitOnce($action, $is_fatal = true)
  * checks whether the user is allowed to do permission. (ie. post_new.)
  * If boards is specified, checks those boards instead of the current one.
  * Always returns true if the user is an administrator.
+ *
  * @param string $permission
  * @param array $boards = null
  * @return bool if the user can do the permission
@@ -961,6 +966,7 @@ function allowedTo($permission, $boards = null)
  * Checks the passed boards or current board for the permission.
  * If they are not, it loads the Errors language file and shows an error using $txt['cannot_' . $permission].
  * If they are a guest and cannot do it, this calls is_not_guest().
+ *
  * @param string $permission
  * @param array $boards = null
  */
@@ -1016,10 +1022,11 @@ function isAllowedTo($permission, $boards = null)
 
 /**
  * Return the boards a user has a certain (board) permission on. (array(0) if all.)
- * returns a list of boards on which the user is allowed to do the specified permission.
- * Returns an array with only a 0 in it if the user has permission to do this on every board.
- * Returns an empty array if he or she cannot do this on any board.
+ *  - returns a list of boards on which the user is allowed to do the specified permission.
+ *  - returns an array with only a 0 in it if the user has permission to do this on every board.
+ *  - returns an empty array if he or she cannot do this on any board.
  * If check_access is true will also make sure the group has proper access to that board.
+ *
  * @param array $permissions
  * @param bool $check_access = true
  * @param bool $simple = true
@@ -1110,6 +1117,7 @@ function boardsAllowedTo($permissions, $check_access = true, $simple = true)
  *  'no_through_forum': don't show the email address, but do allow
  *    things to be mailed using the built-in forum mailer.
  *  'no': keep the email address hidden.
+ *
  * @param bool $userProfile_hideEmail
  * @param int $userProfile_id
  * @return string (yes, yes_permission_override, no_through_forum, no)
@@ -1201,6 +1209,7 @@ function spamProtection($error_type)
 
 /**
  * A generic function to create a pair of index.php and .htaccess files in a directory
+ *
  * @param string $path, the (absolute) directory path
  * @param boolean $attachments, if the directory is an attachments directory or not
  * @return true on success, error string if anything fails
@@ -1271,7 +1280,9 @@ else
 }
 
 /**
- * Another helper function that put together the
+ * Helper function that puts together a ban query for a given ip
+ * builds the query for ipv6, ipv4 or 255.255.255.255 depending on whats supplied
+ *
  * @param string $fullip An IP address either IPv6 or not
  * @return string A SQL condition
  */

+ 1 - 1
Sources/SendTopic.php

@@ -6,7 +6,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Session.php

@@ -11,7 +11,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 8 - 8
Sources/SplitTopics.php

@@ -2,16 +2,16 @@
 
 /**
  * Handle merging and splitting of topics
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
- * 
+ *
  * Original module by Mach8 - We'll never forget you.
  */
 
@@ -181,11 +181,11 @@ function SplitExecute()
 /**
  * allows the user to select the messages to be split.
  * is accessed with ?action=splittopics;sa=selectTopics.
- * uses 'select' sub template of the SplitTopics template or (for 
+ * uses 'select' sub template of the SplitTopics template or (for
  * XMLhttp) the 'split' sub template of the Xml template.
  * supports XMLhttp for adding/removing a message to the selection.
  * uses a session variable to store the selected topics.
- * shows two independent page indexes for both the selected and 
+ * shows two independent page indexes for both the selected and
  * not-selected messages (;topic=1.x;start2=y).
  */
 function SplitSelectTopics()
@@ -965,12 +965,12 @@ function MergeIndex()
 
 /**
  * set merge options and do the actual merge of two or more topics.
- * 
+ *
  * the merge options screen:
  * * shows topics to be merged and allows to set some merge options.
  * * is accessed by ?action=mergetopics;sa=options.and can also internally be called by QuickModeration() (Subs-Boards.php).
  * * uses 'merge_extra_options' sub template of the SplitTopics template.
- * 
+ *
  * the actual merge:
  * * is accessed with ?action=mergetopics;sa=execute.
  * * updates the statistics to reflect the merge.
@@ -1309,7 +1309,7 @@ function MergeExecute($topics = array())
 	while ($row = $smcFunc['db_fetch_row']($request))
 		$affected_msgs[] = $row[0];
 	$smcFunc['db_free_result']($request);
-	
+
 	// Assign the first topic ID to be the merged topic.
 	$id_topic = min($topics);
 

+ 5 - 5
Sources/Stats.php

@@ -2,12 +2,12 @@
 
 /**
  * Provide a display for forum statistics
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -18,7 +18,7 @@ if (!defined('SMF'))
 
 /**
  * Display some useful/interesting board statistics.
- * 
+ *
  * gets all the statistics in order and puts them in.
  * uses the Stats template and language file. (and main sub template.)
  * requires the view_stats permission.
@@ -627,7 +627,7 @@ function DisplayStats()
 		return;
 
 	getDailyStats(implode(' OR ', $condition_text), $condition_params);
-	
+
 	// Custom stats (just add a template_layer to add it to the template!)
  	call_integration_hook('integrate_forum_stats');
 }
@@ -670,7 +670,7 @@ function getDailyStats($condition_string, $condition_parameters = array())
  * only returns anything if stats was enabled during installation.
  * can also be accessed by the admin, to show what stats sm.org collects.
  * does not return any data directly to sm.org, instead starts a new request for security.
- * 
+ *
  * @link http://www.simplemachines.org/about/stats.php for more info.
  */
 function SMStats()

+ 29 - 15
Sources/Subs-Admin.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -73,7 +73,7 @@ function getServerVersions($checkFor)
 		$versions['memcache'] = array('title' => 'Memcached', 'version' => empty($memcached) ? '???' : memcache_get_version($memcached));
 	if (in_array('xcache', $checkFor) && function_exists('xcache_set'))
 		$versions['xcache'] = array('title' => 'XCache', 'version' => XCACHE_VERSION);
-	
+
 	if (in_array('php', $checkFor))
 		$versions['php'] = array('title' => 'PHP', 'version' => PHP_VERSION, 'more' => '?action=admin;area=serversettings;sa=phpinfo');
 
@@ -86,8 +86,8 @@ function getServerVersions($checkFor)
 /**
  * Search through source, theme and language files to determine their version.
  * Get detailed version information about the physical SMF files on the server.
- * 
- * - the input parameter allows to set whether to include SSI.php and whether 
+ *
+ * - the input parameter allows to set whether to include SSI.php and whether
  *   the results should be sorted.
  * - returns an array containing information on source files, templates and
  *   language files found in the default theme directory (grouped by language).
@@ -231,7 +231,7 @@ function getFileVersions(&$versionOptions)
  *
  * The most important function in this file for mod makers happens to be the
  * updateSettingsFile() function, but it shouldn't be used often anyway.
- * 
+ *
  * - updates the Settings.php file with the changes supplied in config_vars.
  * - expects config_vars to be an associative array, with the keys as the
  *   variable names in Settings.php, and the values the variable values.
@@ -240,7 +240,7 @@ function getFileVersions(&$versionOptions)
  * - writes nothing if the resulting file would be less than 10 lines
  *   in length (sanity check for read lock.)
  * - check for changes to db_last_error and passes those off to a separate handler
- * - attempts to create a backup file and will use it should the writing of the 
+ * - attempts to create a backup file and will use it should the writing of the
  *   new settings file fail
  *
  * @param array $config_vars
@@ -248,7 +248,7 @@ function getFileVersions(&$versionOptions)
 function updateSettingsFile($config_vars)
 {
 	global $boarddir, $cachedir, $context;
-	
+
 	// Updating the db_last_error, then don't mess around with Settings.php
 	if (count($config_vars) === 1 && isset($config_vars['db_last_error']))
 	{
@@ -261,7 +261,7 @@ function updateSettingsFile($config_vars)
 
 	// Load the settings file.
 	$settingsArray = trim(file_get_contents($boarddir . '/Settings.php'));
-	
+
 	// Break it up based on \r or \n, and then clean out extra characters.
 	if (strpos($settingsArray, "\n") !== false)
 		$settingsArray = explode("\n", $settingsArray);
@@ -309,10 +309,24 @@ function updateSettingsFile($config_vars)
 
 	// Still more variables to go?  Then lets add them at the end.
 	if (!empty($config_vars))
+<<<<<<< HEAD
 	{		
 		// Add in any newly defined vars that were passed
 		foreach ($config_vars as $var => $val)
 			$settingsArray[$i++] = '$' . $var . ' = ' . $val . ';' . "\n";
+=======
+	{
+		if (trim($settingsArray[$end]) == '?' . '>')
+			$settingsArray[$end++] = '';
+		else
+			$end++;
+
+		// Add in any newly defined vars that were passed
+		foreach ($config_vars as $var => $val)
+			$settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n";
+
+		$settingsArray[$end] = '?' . '>';
+>>>>>>> 0f0f217c462afd53df641ca3381741af9b0585af
 	}
 	else
 		$settingsArray[$i] = trim($settingsArray[$i]);
@@ -329,7 +343,7 @@ function updateSettingsFile($config_vars)
 	// to validate that we even write things on this filesystem.
 	if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
 		$cachedir = $boarddir . '/cache';
-	
+
 	$test_fp = @fopen($cachedir . '/settings_update.tmp', "w+");
 	if ($test_fp)
 	{
@@ -357,7 +371,7 @@ function updateSettingsFile($config_vars)
 		// write out the new
 		$write_settings = implode('', $settingsArray);
 		$written_bytes = file_put_contents($boarddir . '/Settings.php', $write_settings, LOCK_EX);
-		
+
 		// survey says ...
 		if ($written_bytes !== strlen($write_settings) && !$settings_backup_fail)
 		{
@@ -372,15 +386,15 @@ function updateSettingsFile($config_vars)
 
 /**
  * Saves the time of the last db error for the error log
- * - Done separately from updateSettingsFile to avoid race conditions 
+ * - Done separately from updateSettingsFile to avoid race conditions
  *   which can occur during a db error
  * - If it fails Settings.php will assume 0
  */
-function updateDbLastError($time) 
+function updateDbLastError($time)
 {
-	global $boarddir; 
-	
-	// Write out the db_last_error file with the error timestamp 
+	global $boarddir;
+
+	// Write out the db_last_error file with the error timestamp
 	file_put_contents($boarddir . '/db_last_error.php', '<' . '?' . "php\n" . '$db_last_error = ' . $time . ';', LOCK_EX);
 	@touch($boarddir . '/' . 'Settings.php');
 }

+ 33 - 18
Sources/Subs-Auth.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -17,7 +17,7 @@ if (!defined('SMF'))
 	die('Hacking attempt...');
 
 /**
- * sets the SMF-style login cookie and session based on the id_member and password passed.
+ * Sets the SMF-style login cookie and session based on the id_member and password passed.
  * - password should be already encrypted with the cookie salt.
  * - logs the user out if id_member is zero.
  * - sets the cookie and session to last the number of seconds specified by cookie_length.
@@ -193,7 +193,7 @@ function InMaintenance()
  *
  * @param string $type = 'admin'
  */
-function adminLogin($type = 'admin', $additionalToken = false)
+function adminLogin($type = 'admin')
 {
 	global $context, $scripturl, $txt, $user_info, $user_settings;
 
@@ -230,9 +230,6 @@ function adminLogin($type = 'admin', $additionalToken = false)
 	foreach ($_POST as $k => $v)
 		$context['post_data'] .= adminLogin_outputPostVars($k, $v);
 
-	if (!empty($additionalToken))
-		$context['post_data'] .= adminLogin_outputPostVars($context[$additionalToken . '_token_var'], $context[$additionalToken . '_token']);
-
 	// Now we'll use the admin_login sub template of the Login template.
 	$context['sub_template'] = 'admin_login';
 
@@ -250,7 +247,7 @@ function adminLogin($type = 'admin', $additionalToken = false)
 }
 
 /**
- * used by the adminLogin() function.
+ * Used by the adminLogin() function.
  * if 'value' is an array, the function is called recursively.
  *
  * @param string $key
@@ -402,7 +399,7 @@ function findMembers($names, $use_wildcards = false, $buddies_only = false, $max
 }
 
 /**
- * called by index.php?action=findmember.
+ * Called by index.php?action=findmember.
  * - is used as a popup for searching members.
  * - uses sub template find_members of the Help template.
  * - also used to add members for PM's sent using wap2/imode protocol.
@@ -474,7 +471,7 @@ function JSMembers()
 }
 
 /**
- * outputs each member name on its own line.
+ * Outputs each member name on its own line.
  * - used by javascript to find members matching the request.
  */
 function RequestMembers()
@@ -607,26 +604,44 @@ function resetPassword($memID, $username = null)
  * @param string $username
  * @return string Returns null if fine
  */
-function validateUsername($memID, $username)
+function validateUsername($memID, $username, $return_error = false, $check_reserved_name = true)
 {
-	global $sourcedir, $txt;
+	global $sourcedir, $txt, $smcFunc, $user_info;
+
+	$errors = array();
+
+	// Don't use too long a name.
+	if ($smcFunc['strlen']($username) > 25)
+		$errors[] = array('lang', 'error_long_name');
 
 	// No name?!  How can you register with no name?
 	if ($username == '')
-		fatal_lang_error('need_username', false);
+		$errors[] = array('lang', 'need_username');
 
 	// Only these characters are permitted.
 	if (in_array($username, array('_', '|')) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || strpos($username, '[code') !== false || strpos($username, '[/code') !== false)
-		fatal_lang_error('error_invalid_characters_username', false);
+		$errors[] = array('lang', 'error_invalid_characters_username');
 
 	if (stristr($username, $txt['guest_title']) !== false)
-		fatal_lang_error('username_reserved', true, array($txt['guest_title']));
+		$errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title']));
+
+	if ($check_reserved_name)
+	{
+		require_once($sourcedir . '/Subs-Members.php');
+		if (isReservedName($username, $memID, false))
+			$errors[] = array('done', '(' . htmlspecialchars($username) . ') ' . $txt['name_in_use']);
+	}
+
+	if ($return_error)
+		return $errors;
+	elseif (empty($errors))
+		return null;
 
-	require_once($sourcedir . '/Subs-Members.php');
-	if (isReservedName($username, $memID, false))
-		fatal_error('(' . htmlspecialchars($username) . ') ' . $txt['name_in_use'], false);
+	loadLanguage('Errors');
+	$error = $errors[0];
 
-	return null;
+	$message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], $error[3])) : $error[1];
+	fatal_error($message, empty($error[2]) || $user_info['is_admin'] ? false : $error[2]);
 }
 
 /**

+ 33 - 5
Sources/Subs-BoardIndex.php

@@ -8,7 +8,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -53,8 +53,9 @@ function getBoardIndex($boardIndexOptions)
 			' . ($user_info['is_guest'] ? ' 1 AS is_read, 0 AS new_from,' : '
 			(IFNULL(lb.id_msg, 0) >= b.id_msg_updated) AS is_read, IFNULL(lb.id_msg, -1) + 1 AS new_from,' . ($boardIndexOptions['include_categories'] ? '
 			c.can_collapse, IFNULL(cc.id_member, 0) AS is_collapsed,' : '')) . '
-			IFNULL(mem.id_member, 0) AS id_member, m.id_msg,
-			IFNULL(mods_mem.id_member, 0) AS id_moderator, mods_mem.real_name AS mod_real_name
+			IFNULL(mem.id_member, 0) AS id_member, mem.avatar, m.id_msg,
+			IFNULL(mods_mem.id_member, 0) AS id_moderator, mods_mem.real_name AS mod_real_name' . (!empty($settings['avatars_on_indexes']) ? ',
+			IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type' : '') . '
 		FROM {db_prefix}boards AS b' . ($boardIndexOptions['include_categories'] ? '
 			LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)' : '') . '
 			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = b.id_last_msg)
@@ -62,7 +63,8 @@ function getBoardIndex($boardIndexOptions)
 			LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})' . ($boardIndexOptions['include_categories'] ? '
 			LEFT JOIN {db_prefix}collapsed_categories AS cc ON (cc.id_cat = c.id_cat AND cc.id_member = {int:current_member})' : '')) . '
 			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
-			LEFT JOIN {db_prefix}members AS mods_mem ON (mods_mem.id_member = mods.id_member)
+			LEFT JOIN {db_prefix}members AS mods_mem ON (mods_mem.id_member = mods.id_member)' . (!empty($settings['avatars_on_indexes']) ? '
+			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member)' : '') . '
 		WHERE {query_see_board}' . (empty($boardIndexOptions['countChildPosts']) ? (empty($boardIndexOptions['base_level']) ? '' : '
 			AND b.child_level >= {int:child_level}') : '
 			AND b.child_level BETWEEN ' . $boardIndexOptions['base_level'] . ' AND ' . ($boardIndexOptions['base_level'] + 1)),
@@ -102,7 +104,7 @@ function getBoardIndex($boardIndexOptions)
 					'boards' => array(),
 					'new' => false
 				);
-				$categories[$row_board['id_cat']]['link'] = '<a id="c' . $row_board['id_cat'] . '"></a>' . ($categories[$row_board['id_cat']]['can_collapse'] ? '<a href="' . $categories[$row_board['id_cat']]['collapse_href'] . '">' . $row_board['cat_name'] . '</a>' : $row_board['cat_name']);
+				$categories[$row_board['id_cat']]['link'] = '<a id="c' . $row_board['id_cat'] . '"></a>' . (!$context['user']['is_guest'] ? '<a href="' . $scripturl . '?action=unread;c='. $row_board['id_cat'] . '" title="' . sprintf($txt['new_posts_in_category'], strip_tags($row_board['cat_name'])) . '">' . $row_board['cat_name'] . '</a>' : $row_board['cat_name']);
 			}
 
 			// If this board has new posts in it (and isn't the recycle bin!) then the category is new.
@@ -228,6 +230,24 @@ function getBoardIndex($boardIndexOptions)
 		else
 			continue;
 
+		if (!empty($settings['avatars_on_indexes']))
+		{
+			// Allow themers to show the latest poster's avatar along with the board
+			if(!empty($row_board['avatar']))
+			{
+				if ($modSettings['avatar_action_too_large'] == 'option_html_resize' || $modSettings['avatar_action_too_large'] == 'option_js_resize')
+				{
+					$avatar_width = !empty($modSettings['avatar_max_width_external']) ? ' width="' . $modSettings['avatar_max_width_external'] . '"' : '';
+					$avatar_height = !empty($modSettings['avatar_max_height_external']) ? ' height="' . $modSettings['avatar_max_height_external'] . '"' : '';
+				}
+				else
+				{
+					$avatar_width = '';
+					$avatar_height = '';
+				}
+			}
+		}
+
 		// Prepare the subject, and make sure it's not too long.
 		censorText($row_board['subject']);
 		$row_board['short_subject'] = shorten_subject($row_board['subject'], 24);
@@ -247,6 +267,14 @@ function getBoardIndex($boardIndexOptions)
 			'topic' => $row_board['id_topic']
 		);
 
+		if (!empty($settings['avatars_on_indexes']))
+			$this_last_post['member']['avatar'] = array(
+				'name' => $row_board['avatar'],
+				'image' => $row_board['avatar'] == '' ? ($row_board['id_attach'] > 0 ? '<img class="avatar" src="' . (empty($row_board['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $row_board['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $row_board['filename']) . '" alt="" />' : '') : (stristr($row_board['avatar'], 'http://') ? '<img class="avatar" src="' . $row_board['avatar'] . '"' . $avatar_width . $avatar_height . ' alt="" />' : '<img class="avatar" src="' . $modSettings['avatar_url'] . '/' . htmlspecialchars($row_board['avatar']) . '" alt="" />'),
+				'href' => $row_board['avatar'] == '' ? ($row_board['id_attach'] > 0 ? (empty($row_board['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $row_board['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $row_board['filename']) : '') : (stristr($row_board['avatar'], 'http://') ? $row_board['avatar'] : $modSettings['avatar_url'] . '/' . $row_board['avatar']),
+				'url' => $row_board['avatar'] == '' ? '' : (stristr($row_board['avatar'], 'http://') ? $row_board['avatar'] : $modSettings['avatar_url'] . '/' . $row_board['avatar'])
+			);
+
 		// Provide the href and link.
 		if ($row_board['subject'] != '')
 		{

+ 38 - 58
Sources/Subs-Boards.php

@@ -3,12 +3,12 @@
 /**
  * This file is mainly concerned with minor tasks relating to boards, such as
  * marking them read, collapsing categories, or quick moderation.
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -17,50 +17,6 @@
 if (!defined('SMF'))
 	die('Hacking attempt...');
 
-/*
-	void modifyBoard(int board_id, array boardOptions)
-		- general function to modify the settings and position of a board.
-		- used by ManageBoards.php to change the settings of a board.
-
-	int createBoard(array boardOptions)
-		- general function to create a new board and set its position.
-		- allows (almost) the same options as the modifyBoard() function.
-		- with the option inherit_permissions set, the parent board permissions
-		  will be inherited.
-		- returns the ID of the newly created board.
-
-	void deleteBoards(array boards_to_remove, moveChildrenTo = null)
-		- general function to delete one or more boards.
-		- allows to move the children of the board before deleting it
-		- if moveChildrenTo is set to null, the child boards will be deleted.
-		- deletes all topics that are on the given boards.
-		- deletes all information that's associated with the given boards.
-		- updates the statistics to reflect the new situation.
-
-	void reorderBoards()
-		- updates the database to put all boards in the right order.
-		- sorts the records of the boards table.
-		- used by modifyBoard(), deleteBoards(), modifyCategory(), and
-		  deleteCategories() functions.
-
-	void fixChildren(int parent, int newLevel, int newParent)
-		- recursively updates the children of parent's child_level and
-		  id_parent to newLevel and newParent.
-		- used when a board is deleted or moved, to affect its children.
-
-	bool isChildOf(int child, int parent)
-		- determines if child is a child of parent.
-		- recurses down the tree until there are no more parents.
-		- returns true if child is a child of parent.
-
-	void recursiveBoards()
-		- function used by getBoardTree to recursively get a list of boards.
-
-	bool isChildOf(int child, int parent)
-		- determine if a certain board id is a child of another board.
-		- the parent might be several levels higher than the child.
-*/
-
 /**
  * Mark a board or multiple boards read.
  *
@@ -469,6 +425,8 @@ function getMsgMemberID($messageID)
 
 /**
  * Modify the settings and position of a board.
+ * Used by ManageBoards.php to change the settings of a board.
+ *
  * @param int $board_id
  * @param array &$boardOptions
  */
@@ -483,7 +441,8 @@ function modifyBoard($board_id, &$boardOptions)
 	if (!isset($boards[$board_id]) || (isset($boardOptions['target_board']) && !isset($boards[$boardOptions['target_board']])) || (isset($boardOptions['target_category']) && !isset($cat_tree[$boardOptions['target_category']])))
 		fatal_lang_error('no_board');
 
-	call_integration_hook('integrate_modify_board', array($board_id, &$boardOptions));
+	$id = $board_id;
+	call_integration_hook('integrate_pre_modify_board', array($id, &$boardOptions));
 
 	// All things that will be updated in the database will be in $boardUpdates.
 	$boardUpdates = array();
@@ -662,6 +621,9 @@ function modifyBoard($board_id, &$boardOptions)
 		$boardUpdateParameters['num_posts'] = (int) $boardOptions['num_posts'];
 	}
 
+	$id = $board_id;
+	call_integration_hook('integrate_modify_board', array($id, &$boardUpdates, &$boardUpdateParameters));
+
 	// Do the updates (if any).
 	if (!empty($boardUpdates))
 		$request = $smcFunc['db_query']('', '
@@ -752,6 +714,10 @@ function modifyBoard($board_id, &$boardOptions)
 
 /**
  * Create a new board and set its properties and position.
+ * Allows (almost) the same options as the modifyBoard() function.
+ * With the option inherit_permissions set, the parent board permissions
+ * will be inherited.
+ * 
  * @param array $boardOptions
  * @return int The new board id
  */
@@ -766,8 +732,6 @@ function createBoard($boardOptions)
 	if (in_array($boardOptions['move_to'], array('child', 'before', 'after')) && !isset($boardOptions['target_board']))
 		trigger_error('createBoard(): Target board is not set', E_USER_ERROR);
 
-	call_integration_hook('integrate_create_board', array(&$boardOptions));
-
 	// Set every optional value to its default value.
 	$boardOptions += array(
 		'posts_count' => true,
@@ -780,18 +744,22 @@ function createBoard($boardOptions)
 		'inherit_permissions' => true,
 		'dont_log' => true,
 	);
+	$board_columns = array(
+		'id_cat' => 'int', 'name' => 'string-255', 'description' => 'string', 'board_order' => 'int',
+		'member_groups' => 'string', 'redirect' => 'string',
+	);
+	$board_parameters = array(
+		$boardOptions['target_category'], $boardOptions['board_name'] , '', 0,
+		'-1,0', '',
+	);
+
+	call_integration_hook('integrate_create_board', array(&$boardOptions, &$board_columns, &$board_parameters));
 
 	// Insert a board, the settings are dealt with later.
 	$smcFunc['db_insert']('',
 		'{db_prefix}boards',
-		array(
-			'id_cat' => 'int', 'name' => 'string-255', 'description' => 'string', 'board_order' => 'int',
-			'member_groups' => 'string', 'redirect' => 'string',
-		),
-		array(
-			$boardOptions['target_category'], $boardOptions['board_name'] , '', 0,
-			'-1,0', '',
-		),
+		$board_columns,
+		$board_parameters,
 		array('id_board')
 	);
 	$board_id = $smcFunc['db_insert_id']('{db_prefix}boards', 'id_board');
@@ -845,6 +813,13 @@ function createBoard($boardOptions)
 
 /**
  * Remove one or more boards.
+ * Allows to move the children of the board before deleting it
+ * if moveChildrenTo is set to null, the child boards will be deleted.
+ * Deletes:
+ *   - all topics that are on the given boards;
+ *   - all information that's associated with the given boards;
+ * updates the statistics to reflect the new situation.
+ *
  * @param array $boards_to_remove
  * @param array $moveChildrenTo = null
  */
@@ -982,7 +957,8 @@ function deleteBoards($boards_to_remove, $moveChildrenTo = null)
 }
 
 /**
- * Put all boards in the right order.
+ * Put all boards in the right order and sorts the records of the boards table.
+ * Used by modifyBoard(), deleteBoards(), modifyCategory(), and deleteCategories() functions
  */
 function reorderBoards()
 {
@@ -1019,6 +995,8 @@ function reorderBoards()
 
 /**
  * Fixes the children of a board by setting their child_levels to new values.
+ * Used when a board is deleted or moved, to affect its children.
+ *
  * @param int $parent
  * @param int $newLevel
  * @param int $newParent
@@ -1179,6 +1157,8 @@ function getBoardTree()
 
 /**
  * Recursively get a list of boards.
+ * Used by getBoardTree 
+ *
  * @param array &$_boardList
  * @param array &$_tree
  */

+ 42 - 29
Sources/Subs-Calendar.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -172,7 +172,7 @@ function getEventRange($low_date, $high_date, $use_permissions = true)
 					'start_date' => $row['start_date'],
 					'end_date' => $row['end_date'],
 					'is_last' => false,
-					'id_board' => $row['id_board'],	
+					'id_board' => $row['id_board'],
 					'href' => $row['id_board'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0',
 					'link' => $row['id_board'] == 0 ? $row['title'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['title'] . '</a>',
 					'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')),
@@ -869,24 +869,27 @@ function insertEvent(&$eventOptions)
 	$eventOptions['board'] = isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0;
 	$eventOptions['topic'] = isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0;
 
+	$event_columns = array(
+		'id_board' => 'int', 'id_topic' => 'int', 'title' => 'string-60', 'id_member' => 'int',
+		'start_date' => 'date', 'end_date' => 'date',
+	);
+	$event_parameters = array(
+		$eventOptions['board'], $eventOptions['topic'], $eventOptions['title'], $eventOptions['member'],
+		$eventOptions['start_date'], $eventOptions['end_date'],
+	);
+
+	call_integration_hook('integrate_create_event', array(&$eventOptions, &$event_columns, &$event_parameters));
+
 	// Insert the event!
 	$smcFunc['db_insert']('',
 		'{db_prefix}calendar',
-		array(
-			'id_board' => 'int', 'id_topic' => 'int', 'title' => 'string-60', 'id_member' => 'int',
-			'start_date' => 'date', 'end_date' => 'date',
-		),
-		array(
-			$eventOptions['board'], $eventOptions['topic'], $eventOptions['title'], $eventOptions['member'],
-			$eventOptions['start_date'], $eventOptions['end_date'],
-		),
+		$event_columns,
+		$event_parameters,
 		array('id_event')
 	);
 
 	// Store the just inserted id_event for future reference.
 	$eventOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}calendar', 'id_event');
-	
-	call_integration_hook('integrate_insert_event', array($eventOptions));
 
 	// Update the settings to show something calendarish was updated.
 	updateSettings(array(
@@ -904,7 +907,7 @@ function modifyEvent($event_id, &$eventOptions)
 
 	// Properly sanitize the title.
 	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
-	
+
 	// Scan the start date for validity and get its components.
 	if (($num_results = sscanf($eventOptions['start_date'], '%d-%d-%d', $year, $month, $day)) !== 3)
 		trigger_error('modifyEvent(): invalid start date format given', E_USER_ERROR);
@@ -916,25 +919,35 @@ function modifyEvent($event_id, &$eventOptions)
 	if (!isset($eventOptions['end_date']))
 		$eventOptions['end_date'] = strftime('%Y-%m-%d', mktime(0, 0, 0, $month, $day, $year) + $eventOptions['span'] * 86400);
 
-	
-	call_integration_hook('integrate_modify_event', array($event_id, &$eventOptions));		
-		
+	$event_columns = array(
+		'start_date' => '{date:start_date}',
+		'end_date' => '{date:end_date}',
+		'title' => 'SUBSTRING({string:title}, 1, 60)',
+		'id_board' => '{int:id_board}',
+		'id_topic' => '{int:id_topic}'
+	);
+	$event_parameters = array(
+		'start_date' => $eventOptions['start_date'],
+		'end_date' => $eventOptions['end_date'],
+		'title' => $eventOptions['title'],
+		'id_board' => isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0,
+		'id_topic' => isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0,
+	);
+
+	// This is to prevent hooks to modify the id of the event
+	$real_event_id = $event_id;
+	call_integration_hook('integrate_modify_event', array($event_id, &$eventOptions, &$event_columns, &$event_parameters));
+
 	$smcFunc['db_query']('', '
 		UPDATE {db_prefix}calendar
 		SET
-			start_date = {date:start_date},
-			end_date = {date:end_date},
-			title = SUBSTRING({string:title}, 1, 60),
-			id_board = {int:id_board},
-			id_topic = {int:id_topic}
+			' . implode(', ', $event_columns) . '
 		WHERE id_event = {int:id_event}',
-		array(
-			'start_date' => $eventOptions['start_date'],
-			'end_date' => $eventOptions['end_date'],
-			'title' => $eventOptions['title'],
-			'id_board' => isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0,
-			'id_topic' => isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0,
-			'id_event' => $event_id,
+		array_merge(
+			$event_parameters,
+			array(
+				'id_event' => $real_event_id
+			)
 		)
 	);
 
@@ -958,7 +971,7 @@ function removeEvent($event_id)
 			'id_event' => $event_id,
 		)
 	);
-	
+
 	call_integration_hook('integrate_remove_event', array($event_id));
 
 	updateSettings(array(

+ 24 - 17
Sources/Subs-Categories.php

@@ -2,12 +2,12 @@
 
 /**
  * This file contains the functions to add, modify, remove, collapse and expand categories.
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -20,7 +20,7 @@ if (!defined('SMF'))
  * Edit the position and properties of a category.
  * general function to modify the settings and position of a category.
  * used by ManageBoards.php to change the settings of a category.
- * 
+ *
  * @param int $category_id
  * @param array $catOptions
  */
@@ -31,7 +31,8 @@ function modifyCategory($category_id, $catOptions)
 	$catUpdates = array();
 	$catParameters = array();
 
-	call_integration_hook('integrate_modify_category', array($category_id, &$catOptions));
+	$cat_id = $category_id;
+	call_integration_hook('integrate_pre_modify_category', array($cat_id, &$catOptions));
 
 	// Wanna change the categories position?
 	if (isset($catOptions['move_after']))
@@ -93,6 +94,9 @@ function modifyCategory($category_id, $catOptions)
 		$catParameters['is_collapsible'] = $catOptions['is_collapsible'] ? 1 : 0;
 	}
 
+	$cat_id = $category_id;
+	call_integration_hook('integrate_modify_category', array($cat_id, &$catUpdates, &$catParameters));
+
 	// Do the updates (if any).
 	if (!empty($catUpdates))
 	{
@@ -117,7 +121,7 @@ function modifyCategory($category_id, $catOptions)
  * general function to create a new category and set its position.
  * allows (almost) the same options as the modifyCat() function.
  * returns the ID of the newly created category.
- * 
+ *
  * @param int $createCategory
  * @param array $catOptions
  */
@@ -125,8 +129,6 @@ function createCategory($catOptions)
 {
 	global $smcFunc;
 
-	call_integration_hook('integrate_create_category', array(&$catOptions));
-
 	// Check required values.
 	if (!isset($catOptions['cat_name']) || trim($catOptions['cat_name']) == '')
 		trigger_error('createCategory(): A category name is required', E_USER_ERROR);
@@ -139,15 +141,20 @@ function createCategory($catOptions)
 	// Don't log an edit right after.
 	$catOptions['dont_log'] = true;
 
+	$cat_columns = array(
+		'name' => 'string-48',
+	);
+	$cat_parameters = array(
+		$catOptions['cat_name'],
+	);
+	
+	call_integration_hook('integrate_create_category', array(&$catOptions, &$cat_columns, &$cat_parameters));
+
 	// Add the category to the database.
 	$smcFunc['db_insert']('',
 		'{db_prefix}categories',
-		array(
-			'name' => 'string-48',
-		),
-		array(
-			$catOptions['cat_name'],
-		),
+		$cat_columns,
+		$cat_parameters,
 		array('id_cat')
 	);
 
@@ -163,14 +170,14 @@ function createCategory($catOptions)
 	return $category_id;
 }
 
-/** 
+/**
  * Remove one or more categories.
  * general function to delete one or more categories.
  * allows to move all boards in the categories to a different category before deleting them.
  * if moveChildrenTo is set to null, all boards inside the given categorieswill be deleted.
  * deletes all information that's associated with the given categories.
  * updates the statistics to reflect the new situation.
- * 
+ *
  * @param array $categories_to_remove
  * @param int $moveChildrenTo = null
  */
@@ -246,12 +253,12 @@ function deleteCategories($categories, $moveBoardsTo = null)
 	reorderBoards();
 }
 
-/** 
+/**
  * Collapse, expand or toggle one or more categories for one or more members.
  * if members is null, the category is collapsed/expanded for all members.
  * allows three changes to the status: 'expand', 'collapse' and 'toggle'.
  * if check_collapsable is set, only category allowed to be collapsed, will be collapsed.
- * 
+ *
  * @param array $categories
  * @param string $new_status
  * @param array $members = null

+ 1 - 1
Sources/Subs-Charset.php

@@ -5,7 +5,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Subs-Compat.php

@@ -10,7 +10,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 4 - 4
Sources/Subs-Db-mysql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -124,7 +124,7 @@ function smf_db_replacement__callback($matches)
 	global $db_callback, $user_info, $db_prefix;
 
 	list ($values, $connection) = $db_callback;
-	
+
 	// Connection gone???  This should *never* happen at this point, yet it does :'(
 	if (!is_resource($connection))
 		display_db_error();
@@ -390,7 +390,7 @@ function smf_db_query($identifier, $db_string, $db_values = array(), $connection
 		$ret = @mysql_query($db_string, $connection);
 	else
 		$ret = @mysql_unbuffered_query($db_string, $connection);
-		
+
 	if ($ret === false && empty($db_values['db_error_skip']))
 		$ret = smf_db_error($db_string, $connection);
 
@@ -783,4 +783,4 @@ function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false
 		);
 
 	return strtr($string, $replacements);
-}
+}

+ 1 - 1
Sources/Subs-Db-postgresql.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1

+ 2 - 2
Sources/Subs-Db-sqlite.php

@@ -7,7 +7,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -848,4 +848,4 @@ function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false
 		);
 
 	return strtr($string, $replacements);
-}
+}

+ 30 - 31
Sources/Subs-Editor.php

@@ -3,12 +3,12 @@
 /**
  * This file contains those functions specific to the editing box and is
  * generally used for WYSIWYG type functionality.
- * 
+ *
  * Simple Machines Forum (SMF)
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -21,7 +21,7 @@ if (!defined('SMF'))
  * !!!Compatibility!!!
  * Since we changed the editor we don't need it any more, but let's keep it if any mod wants to use it
  * Convert only the BBC that can be edited in HTML mode for the editor.
- * 
+ *
  * @param string $text
  * @param boolean $compat_mode if true will convert the text, otherwise not (default false)
  * @return string
@@ -85,9 +85,9 @@ function bbc_to_html($text, $compat_mode = false)
  * !!!Compatibility!!!
  * This is no more needed, but to avoid break mods let's keep it
  * Run it it shouldn't even hurt either, so let's not bother remove it
- * 
+ *
  * The harder one - wysiwyg to BBC!
- * 
+ *
  * @param string $text
  * @return string
  */
@@ -849,9 +849,9 @@ function html_to_bbc($text)
 /**
  * !!!Compatibility!!!
  * This is no more needed, but to avoid break mods let's keep it
- * 
+ *
  * Returns an array of attributes associated with a tag.
- * 
+ *
  * @param string $text
  * @return string
  */
@@ -1332,7 +1332,7 @@ function loadLocale()
 	else
 		@ob_start();
 
-	// If we don't have any locale better avoit broken js
+	// If we don't have any locale better avoid broken js
 	if (empty($txt['lang_locale']))
 		die();
 
@@ -1361,7 +1361,7 @@ function loadLocale()
  *   message icons or a list of custom message icons retrieved from the database.
  * - The board_id is needed for the custom message icons (which can be set for
  *   each board individually).
- * 
+ *
  * @param int $board_id
  * @return array
  */
@@ -1435,7 +1435,7 @@ function getMessageIcons($board_id)
 
 /**
  * Compatibility function - used in 1.1 for showing a post box.
- * 
+ *
  * @param string $msg
  * @return string
  */
@@ -1469,28 +1469,25 @@ 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');
-		$context['html_headers'] .= '
-		<script type="text/javascript"><!-- // --><![CDATA[
-			var smf_smileys_url = \'' . $settings['smileys_url'] . '\';
-			var bbc_quote_from = \'' . addcslashes($txt['quote_from'], "'") . '\';
-			var bbc_quote = \'' . addcslashes($txt['quote'], "'") . '\';
-			var bbc_search_on = \'' . addcslashes($txt['search_on'], "'") . '\';
-		// ]]></script>
-		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/editor.js?alp21"></script>
-		<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/jquery.sceditor.css" type="text/css" media="all" />
-		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/jquery.sceditor.js"></script>
-		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/jquery.sceditor.bbcode.js"></script>';
-
+		loadTemplate('GenericControls', 'jquery.sceditor');
+
+		// 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'], "'") . '\';
+		var bbc_quote = \'' . addcslashes($txt['quote'], "'") . '\';
+		var bbc_search_on = \'' . addcslashes($txt['search_on'], "'") . '\';');
+		// editor language file
 		if (!empty($txt['lang_locale']) && $txt['lang_locale'] != 'en_US')
-			$context['html_headers'] .= '
-		<script type="text/javascript" src="' . $scripturl . '?action=loadeditorlocale"></script>';
+			loadJavascriptFile($scripturl . '?action=loadeditorlocale', array(), 'sceditor_language');
 
 		$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
 		if ($context['show_spellchecking'])
 		{
-			$context['html_headers'] .= '
-		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/spellcheck.js?alp21"></script>';
+			loadJavascriptFile('spellcheck.js', array('default_theme' => true));
 
 			// Some hidden information is needed in order to make the spell checking work.
 			if (!isset($_REQUEST['xml']))
@@ -1709,7 +1706,7 @@ function create_control_richedit($editorOptions)
 		}
 
 		$bbcodes_styles = '';
-		$context['bbcodes_hanlders'] = '';
+		$context['bbcodes_handlers'] = '';
 		$context['bbc_toolbar'] = array();
 		foreach ($context['bbc_tags'] as $row => $tagRow)
 		{
@@ -1730,7 +1727,7 @@ function create_control_richedit($editorOptions)
 			}';
 						if (isset($tag['before']))
 						{
-							$context['bbcodes_hanlders'] = '
+							$context['bbcodes_handlers'] = '
 				$.sceditor.setCommand(
 					' . javaScriptEscape($tag['code']) . ',
 					function () {
@@ -2166,6 +2163,8 @@ function AutoSuggestHandler($checkRegistered = null)
 		'versions' => 'SMFVersions',
 	);
 
+	call_integration_hook('integrate_autosuggest', array($searchTypes));
+
 	// If we're just checking the callback function is registered return true or false.
 	if ($checkRegistered != null)
 		return isset($searchTypes[$checkRegistered]) && function_exists('AutoSuggest_Search_' . $checkRegistered);
@@ -2186,7 +2185,7 @@ function AutoSuggestHandler($checkRegistered = null)
 
 /**
  * Search for a member - by real_name or member_name by default.
- * 
+ *
  * @return string
  */
 function AutoSuggest_Search_Member()
@@ -2282,4 +2281,4 @@ function AutoSuggest_Search_SMFVersions()
 			);
 
 	return $xml_data;
-}
+}

+ 7 - 7
Sources/Subs-Graphics.php

@@ -12,7 +12,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -65,7 +65,7 @@ function downloadAvatar($url, $memID, $max_width, $max_height)
 		array('id_attach')
 	);
 	$attachID = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
-	
+
 	// Retain this globally in case the script wants it.
 	$modSettings['new_avatar_data'] = array(
 		'id' => $attachID,
@@ -282,7 +282,7 @@ function checkImagick()
 function imageMemoryCheck($sizes)
 {
 	global $modSettings;
-	
+
 	// doing the old 'set it and hope' way?
 	if (empty($modSettings['attachment_thumb_memory']))
 	{
@@ -291,10 +291,10 @@ function imageMemoryCheck($sizes)
 	}
 
 	// Determine the memory requirements for this image, note: if you want to use an image formula W x H x bits/8 x channels x Overhead factor
-	// you will need to account for single bit images as GD expands them to an 8 bit and will greatly overun the calculated value.  The 5 is 
+	// you will need to account for single bit images as GD expands them to an 8 bit and will greatly overun the calculated value.  The 5 is
 	// simply a shortcut of 8bpp, 3 channels, 1.66 overhead
 	$needed_memory = ($sizes[0] * $sizes[1] * 5);
-	
+
 	// if we need more, lets try to get it
 	return setMemoryLimit($needed_memory, true);
 }
@@ -359,7 +359,7 @@ function resizeImageFile($source, $destination, $max_width, $max_height, $prefer
 	// We can't get to the file.
 	else
 		$sizes = array(-1, -1, -1);
-		
+
 	// See if we have -or- can get the needed memory for this operation
 	if (checkGD() && !imageMemoryCheck($sizes))
 		return false;
@@ -710,7 +710,7 @@ if (!function_exists('imagecreatefrombmp'))
 				for ($j = 0; $j < $scan_line_size; $x++)
 				{
 					$byte = ord($scan_line{$j++});
-					
+
 					imagesetpixel($dst_img, $x, $y, $palette[(($byte) & 128) != 0]);
 					for ($shift = 1; $shift < 8; $shift++) {
 						if (++$x < $info['width']) imagesetpixel($dst_img, $x, $y, $palette[(($byte << $shift) & 128) != 0]);

+ 5 - 3
Sources/Subs-List.php

@@ -6,7 +6,7 @@
  *
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  * @version 2.1 Alpha 1
@@ -31,6 +31,8 @@ function createList($listOptions)
 	assert((empty($listOptions['default_sort_col']) || isset($listOptions['columns'][$listOptions['default_sort_col']])));
 	assert((!isset($listOptions['form']) || isset($listOptions['form']['href'])));
 
+	call_integration_hook('integrate_' . $listOptions['id'], array(&$listOptions));
+
 	// All the context data will be easily accessible by using a reference.
 	$context[$listOptions['id']] = array();
 	$list_context = &$context[$listOptions['id']];
@@ -151,9 +153,9 @@ function createList($listOptions)
 
 			// Allow for basic formatting.
 			if (!empty($column['data']['comma_format']))
-				$cur_data['value'] = comma_format($list_item[$column['data']['comma_format']]);
+				$cur_data['value'] = comma_format($cur_data['value']);
 			elseif (!empty($column['data']['timeformat']))
-				$cur_data['value'] = timeformat($list_item[$column['data']['timeformat']]);
+				$cur_data['value'] = timeformat($cur_data['value']);
 
 			// Set a style class for this column?
 			if (isset($column['data']['class']))

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä