Browse Source

Fixed conflicts

Jeremy D 11 years ago
parent
commit
2a7cea57b6
100 changed files with 3732 additions and 1403 deletions
  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
 cache/data*.php
 Packages/backups/*
 Packages/backups/*
 Packages/temp
 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.
 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.
 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:
 ######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).
 * 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.
 * 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))
  * 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.
 * 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. ;)
 Finally, feel free to play around. That's what we're doing. ;)
 
 

+ 64 - 15
SSI.php

@@ -5,7 +5,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -147,6 +147,10 @@ loadPermissions();
 // Load the current or SSI theme. (just use $ssi_theme = id_theme;)
 // Load the current or SSI theme. (just use $ssi_theme = id_theme;)
 loadTheme(isset($ssi_theme) ? (int) $ssi_theme : 0);
 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.
 // Take care of any banning that needs to be done.
 if (isset($_REQUEST['ssi_ban']) || (isset($ssi_ban) && $ssi_ban === true))
 if (isset($_REQUEST['ssi_ban']) || (isset($ssi_ban) && $ssi_ban === true))
 	is_not_banned();
 	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.
 // 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;
 	global $user_info, $modSettings;
 
 
+	if (empty($post_ids))
+		return;
+
 	// Allow the user to request more than one - why not?
 	// Allow the user to request more than one - why not?
 	$post_ids = is_array($post_ids) ? $post_ids : array($post_ids);
 	$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.
 // 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 $context, $settings, $scripturl, $txt, $db_prefix, $user_info;
 	global $modSettings, $smcFunc;
 	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}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_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})' : '') . '
 			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 . '
 		ORDER BY ' . $query_order . '
 		' . ($query_limit == '' ? '' : 'LIMIT ' . $query_limit),
 		' . ($query_limit == '' ? '' : 'LIMIT ' . $query_limit),
 		array_merge($query_where_params, array(
 		array_merge($query_where_params, array(
 			'current_member' => $user_info['id'],
 			'current_member' => $user_info['id'],
+			'is_approved' => 1,
 		))
 		))
 	);
 	);
 	$posts = array();
 	$posts = array();
@@ -792,8 +803,11 @@ function ssi_randomMember($random_type = '', $output_method = 'echo')
 }
 }
 
 
 // Fetch a specific member.
 // 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...
 	// Can have more than one member if you really want...
 	$member_ids = is_array($member_ids) ? $member_ids : array($member_ids);
 	$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.
 // 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 = '
 	$query_where = '
 		id_group = {int:id_group}
 		id_group = {int:id_group}
 		OR id_post_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!
 // 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 $context, $settings, $scripturl, $txt, $db_prefix, $user_info;
 	global $modSettings, $smcFunc, $memberContext;
 	global $modSettings, $smcFunc, $memberContext;
 
 
+	if ($query_where === null)
+		return;
+
 	// Fetch the members in question.
 	// Fetch the members in question.
 	$request = $smcFunc['db_query']('', '
 	$request = $smcFunc['db_query']('', '
 		SELECT id_member
 		SELECT id_member
@@ -892,6 +912,9 @@ function ssi_boardStats($output_method = 'echo')
 {
 {
 	global $db_prefix, $txt, $scripturl, $modSettings, $smcFunc;
 	global $db_prefix, $txt, $scripturl, $modSettings, $smcFunc;
 
 
+	if (!allowedTo('view_stats'))
+		return;
+
 	$totals = array(
 	$totals = array(
 		'members' => $modSettings['totalMembers'],
 		'members' => $modSettings['totalMembers'],
 		'posts' => $modSettings['totalMessages'],
 		'posts' => $modSettings['totalMessages'],
@@ -1451,6 +1474,9 @@ function ssi_quickSearch($output_method = 'echo')
 {
 {
 	global $scripturl, $txt, $context;
 	global $scripturl, $txt, $context;
 
 
+	if (!allowedTo('search_posts'))
+		return;
+
 	if ($output_method != 'echo')
 	if ($output_method != 'echo')
 		return $scripturl . '?action=search';
 		return $scripturl . '?action=search';
 
 
@@ -1476,6 +1502,9 @@ function ssi_todaysBirthdays($output_method = 'echo')
 {
 {
 	global $scripturl, $modSettings, $user_info;
 	global $scripturl, $modSettings, $user_info;
 
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view') || !allowedTo('profile_view_any'))
+		return;
+
 	$eventOptions = array(
 	$eventOptions = array(
 		'include_birthdays' => true,
 		'include_birthdays' => true,
 		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
 		'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;
 	global $modSettings, $user_info;
 
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	$eventOptions = array(
 	$eventOptions = array(
 		'include_holidays' => true,
 		'include_holidays' => true,
 		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
 		'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;
 	global $modSettings, $user_info;
 
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	$eventOptions = array(
 	$eventOptions = array(
 		'include_events' => 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'],
 		'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;
 	global $modSettings, $txt, $scripturl, $user_info;
 
 
+	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
+		return;
+
 	$eventOptions = array(
 	$eventOptions = array(
-		'include_birthdays' => true,
+		'include_birthdays' => allowedTo('profile_view_any'),
 		'include_holidays' => true,
 		'include_holidays' => true,
 		'include_events' => 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'],
 		'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.
 	// Find the post ids.
 	$request = $smcFunc['db_query']('', '
 	$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,
 		LIMIT ' . $start . ', ' . $limit,
 		array(
 		array(
 			'current_board' => $board,
 			'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)
 		if (!empty($length) && $smcFunc['strlen']($row['body']) > $length)
 		{
 		{
 			$row['body'] = $smcFunc['substr']($row['body'], 0, $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)
 			if ($cutoff !== false)
 				$row['body'] = $smcFunc['substr']($row['body'], 0, $cutoff);
 				$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;
 	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.
 	// Find all events which are happening in the near future that the member can see.
 	$request = $smcFunc['db_query']('', '
 	$request = $smcFunc['db_query']('', '
 		SELECT
 		SELECT

+ 13 - 15
Sources/Admin.php

@@ -8,7 +8,7 @@
  * @package SMF
  * @package SMF
  * @author Simple Machines
  * @author Simple Machines
  *
  *
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -30,7 +30,7 @@ function AdminMain()
 	// Load the language and templates....
 	// Load the language and templates....
 	loadLanguage('Admin');
 	loadLanguage('Admin');
 	loadTemplate('Admin', '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.
 	// No indexing evil stuff.
 	$context['robot_no_index'] = true;
 	$context['robot_no_index'] = true;
@@ -39,14 +39,6 @@ function AdminMain()
 
 
 	// Some preferences.
 	// Some preferences.
 	$context['admin_preferences'] = !empty($options['admin_preferences']) ? unserialize($options['admin_preferences']) : array();
 	$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!
 	// Define all the menu structure - see Subs-Menu.php for details!
 	$admin_areas = array(
 	$admin_areas = array(
@@ -189,7 +181,7 @@ function AdminMain()
 					'function' => 'ModifyModSettings',
 					'function' => 'ModifyModSettings',
 					'icon' => 'modifications.png',
 					'icon' => 'modifications.png',
 					'subsections' => array(
 					'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']),
 						'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:
 						// Mod Authors for a "ADD AFTER" on this line. Ensure you end your change with a comma. For example:
 						// 'shout' => array($txt['shout']),
 						// 'shout' => array($txt['shout']),
@@ -227,6 +219,14 @@ function AdminMain()
 						'topics' => array($txt['manageposts_topic_settings']),
 						'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(
 				'managecalendar' => array(
 					'label' => $txt['manage_calendar'],
 					'label' => $txt['manage_calendar'],
 					'file' => 'ManageCalendar.php',
 					'file' => 'ManageCalendar.php',
@@ -276,6 +276,7 @@ function AdminMain()
 						'browse' => array($txt['attachment_manager_browse']),
 						'browse' => array($txt['attachment_manager_browse']),
 						'attachments' => array($txt['attachment_manager_settings']),
 						'attachments' => array($txt['attachment_manager_settings']),
 						'avatars' => array($txt['attachment_manager_avatar_settings']),
 						'avatars' => array($txt['attachment_manager_avatar_settings']),
+						'attachpaths' => array($txt['attach_directories']),
 						'maintenance' => array($txt['attachment_manager_maintenance']),
 						'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...
 	// Make sure the administrator has a valid session...
 	validateSession();
 	validateSession();
 
 
@@ -995,5 +993,5 @@ function AdminEndSession()
 		if (strpos($key, '-admin') !== false)
 		if (strpos($key, '-admin') !== false)
 			unset($_SESSION['token'][$key]);
 			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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -108,14 +108,14 @@ function BoardIndex()
 		$context['show_calendar'] = false;
 		$context['show_calendar'] = false;
 
 
 	$context['page_title'] = sprintf($txt['forum_index'], $context['forum_name']);
 	$context['page_title'] = sprintf($txt['forum_index'], $context['forum_name']);
-	
+
 	// Mark read button
 	// Mark read button
 	$context['mark_read_button'] = array(
 	$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']),
 		'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
 	// 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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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'],
 			'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']
 			'name' => $txt['calendar_week'] . ' ' . $context['calendar_grid_main']['week_number']
 		);
 		);
-		
+
 	// Build the calendar button array.
 	// Build the calendar button array.
 	$context['calendar_buttons'] = 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']),
 		'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
 	// Allow mods to add additional buttons here
 	call_integration_hook('integrate_calendar_buttons');
 	call_integration_hook('integrate_calendar_buttons');
 }
 }
@@ -234,7 +234,7 @@ function CalendarPost()
 				list ($id_board, $id_topic) = $smcFunc['db_fetch_row']($request);
 				list ($id_board, $id_topic) = $smcFunc['db_fetch_row']($request);
 				$smcFunc['db_free_result']($request);
 				$smcFunc['db_free_result']($request);
 			}
 			}
-			
+
 			$eventOptions = array(
 			$eventOptions = array(
 				'title' => substr($_REQUEST['evtitle'], 0, 60),
 				'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),
 				'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.
 	// You can't export if the calendar export feature is off.
 	if (empty($modSettings['cal_export']))
 	if (empty($modSettings['cal_export']))
 		fatal_lang_error('calendar_export_off', false);
 		fatal_lang_error('calendar_export_off', false);
-	
+
 	// Goes without saying that this is required.
 	// Goes without saying that this is required.
 	if (!isset($_REQUEST['eventid']))
 	if (!isset($_REQUEST['eventid']))
 		fatal_lang_error('no_access', false);
 		fatal_lang_error('no_access', false);
@@ -352,7 +352,7 @@ function iCalDownload()
 
 
 	// Load up the event in question and check it exists.
 	// Load up the event in question and check it exists.
 	$event = getEventProperties($_REQUEST['eventid']);
 	$event = getEventProperties($_REQUEST['eventid']);
-	
+
 	if ($event === false)
 	if ($event === false)
 		fatal_lang_error('no_access', false);
 		fatal_lang_error('no_access', false);
 
 
@@ -387,15 +387,15 @@ function iCalDownload()
 	$filecontents .= 'ORGANIZER;CN="' . $event['realname'] . '":MAILTO:' . $webmaster_email . "\n";
 	$filecontents .= 'ORGANIZER;CN="' . $event['realname'] . '":MAILTO:' . $webmaster_email . "\n";
 	$filecontents .= 'DTSTAMP:' . $datestamp . "\n";
 	$filecontents .= 'DTSTAMP:' . $datestamp . "\n";
 	$filecontents .= 'DTSTART;VALUE=DATE:' . $datestart . "\n";
 	$filecontents .= 'DTSTART;VALUE=DATE:' . $datestart . "\n";
-	
+
 	// more than one day
 	// more than one day
 	if ($event['span'] > 1)
 	if ($event['span'] > 1)
 		$filecontents .= 'DTEND;VALUE=DATE:' . $dateend . "\n";
 		$filecontents .= 'DTEND;VALUE=DATE:' . $dateend . "\n";
-	
+
 	// event has changed? advance the sequence for this UID
 	// event has changed? advance the sequence for this UID
 	if ($event['sequence'] > 0)
 	if ($event['sequence'] > 0)
 		$filecontents .= 'SEQUENCE:' . $event['sequence'] . "\n";
 		$filecontents .= 'SEQUENCE:' . $event['sequence'] . "\n";
-	
+
 	$filecontents .= 'SUMMARY:' . implode('', $title);
 	$filecontents .= 'SUMMARY:' . implode('', $title);
 	$filecontents .= 'UID:' . $event['eventid'] . '@' . str_replace(' ', '-', $mbname) . "\n";
 	$filecontents .= 'UID:' . $event['eventid'] . '@' . str_replace(' ', '-', $mbname) . "\n";
 	$filecontents .= 'END:VEVENT' . "\n";
 	$filecontents .= 'END:VEVENT' . "\n";
@@ -407,7 +407,7 @@ function iCalDownload()
 		@ob_start('ob_gzhandler');
 		@ob_start('ob_gzhandler');
 	else
 	else
 		ob_start();
 		ob_start();
-	
+
 	// Send the file headers
 	// Send the file headers
 	header('Pragma: ');
 	header('Pragma: ');
 	header('Cache-Control: no-cache');
 	header('Cache-Control: no-cache');
@@ -420,7 +420,7 @@ function iCalDownload()
 	header('Content-Disposition: attachment; filename="' . $event['title'] . '.ics"');
 	header('Content-Disposition: attachment; filename="' . $event['title'] . '.ics"');
 	if (empty($modSettings['enableCompressedOutput']))
 	if (empty($modSettings['enableCompressedOutput']))
 		header('Content-Length: ' . $smcFunc['strlen']($filecontents));
 		header('Content-Length: ' . $smcFunc['strlen']($filecontents));
-	
+
 	// This is a calendar item!
 	// This is a calendar item!
 	header('Content-Type: text/calendar');
 	header('Content-Type: text/calendar');
 
 

+ 37 - 16
Sources/Class-BrowserDetect.php

@@ -1,20 +1,30 @@
 <?php
 <?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
  * - Detects the following browsers
  * - Opera, Webkit, Firefox, Web_tv, Konqueror, IE, Gecko
  * - Opera, Webkit, Firefox, Web_tv, Konqueror, IE, Gecko
  * - Webkit variants: Chrome, iphone, blackberry, android, safari, ipad, ipod
  * - 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 ...
  * - Firefox Versions: 1, 2, 3 .... 11 ...
  * - Chrome Versions: 1 ... 18 ...
  * - 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
 class browser_detector
 {
 {
@@ -53,7 +63,7 @@ class browser_detector
 		// Old friend, old frenemy
 		// Old friend, old frenemy
 		elseif ($this->isIe())
 		elseif ($this->isIe())
 			$this->setupIe();
 			$this->setupIe();
-		
+
 		// Just a few mobile checks
 		// Just a few mobile checks
 		$this->isOperaMini();
 		$this->isOperaMini();
 		$this->isOperaMobi();
 		$this->isOperaMobi();
@@ -122,7 +132,7 @@ class browser_detector
 	function isFirefox()
 	function isFirefox()
 	{
 	{
 		if (!isset($this->_browsers['is_firefox']))
 		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'];
 		return $this->_browsers['is_firefox'];
 	}
 	}
 
 
@@ -319,18 +329,21 @@ class browser_detector
 			$context['browser_body_id'] = 'mobile';
 			$context['browser_body_id'] = 'mobile';
 		else
 		else
 		{
 		{
+			// add in any specific detection conversions here if you want a special body id e.g. 'is_opera9' => 'opera9'
 			$browser_priority = array(
 			$browser_priority = array(
 				'is_ie6' => 'ie6',
 				'is_ie6' => 'ie6',
 				'is_ie7' => 'ie7',
 				'is_ie7' => 'ie7',
+				'is_ie8' => 'ie8',
+				'is_ie9' => 'ie9',
+				'is_ie10' => 'ie10',
 				'is_ie' => 'ie',
 				'is_ie' => 'ie',
-				'is_firefox3' => 'firefox3',
-				'is_firefox4' => 'firefox4',
 				'is_firefox' => 'firefox',
 				'is_firefox' => 'firefox',
 				'is_chrome' => 'chrome',
 				'is_chrome' => 'chrome',
 				'is_safari' => 'safari',
 				'is_safari' => 'safari',
-				'is_opera8' => 'opera8',
-				'is_opera9' => 'opera9',
 				'is_opera10' => 'opera10',
 				'is_opera10' => 'opera10',
+				'is_opera11' => 'opera11',
+				'is_opera12' => 'opera12',
+				'is_opera' => 'opera',
 				'is_konqueror' => 'konqueror',
 				'is_konqueror' => 'konqueror',
 			);
 			);
 
 
@@ -354,14 +367,17 @@ class browser_detector
 	function fillInformation()
 	function fillInformation()
 	{
 	{
 		$this->_browsers += array(
 		$this->_browsers += array(
-			'is_webkit' => false,
+			'is_opera' => false,
 			'is_opera6' => false,
 			'is_opera6' => false,
 			'is_opera7' => false,
 			'is_opera7' => false,
 			'is_opera8' => false,
 			'is_opera8' => false,
 			'is_opera9' => false,
 			'is_opera9' => false,
 			'is_opera10' => false,
 			'is_opera10' => false,
-			'is_ie4' => false,
+			'is_webkit' => false,
 			'is_mac_ie' => false,
 			'is_mac_ie' => false,
+			'is_web_tv' => false,
+			'is_konqueror' => false,
+			'is_firefox' => false,
 			'is_firefox1' => false,
 			'is_firefox1' => false,
 			'is_firefox2' => false,
 			'is_firefox2' => false,
 			'is_firefox3' => false,
 			'is_firefox3' => false,
@@ -369,12 +385,17 @@ class browser_detector
 			'is_android' => false,
 			'is_android' => false,
 			'is_chrome' => false,
 			'is_chrome' => false,
 			'is_safari' => false,
 			'is_safari' => false,
+			'is_gecko'  => false,
 			'is_ie8' => false,
 			'is_ie8' => false,
 			'is_ie7' => false,
 			'is_ie7' => false,
 			'is_ie6' => false,
 			'is_ie6' => false,
 			'is_ie5.5' => false,
 			'is_ie5.5' => false,
 			'is_ie5' => false,
 			'is_ie5' => false,
+			'is_ie' => false,
+			'is_ie4' => false,
 			'ie_standards_fix' => false,
 			'ie_standards_fix' => false,
+			'needs_size_fix' => false,
+			'possibly_robot' => false,
 		);
 		);
 	}
 	}
 }
 }

+ 28 - 17
Sources/Class-CurlFetchWeb.php

@@ -1,7 +1,19 @@
 <?php
 <?php
 /**
 /**
- *
  * Simple Machines Forum (SMF)
  * 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
  * Simple cURL class to fetch a web page
  * Properly redirects even with safe mode and basedir restrictions
  * Properly redirects even with safe mode and basedir restrictions
  * Can provide simple post options to a page
  * 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', array('user' => 'name', 'password' => 'password')); // post to a page
  *  - $fetch_data('http://www.simplemachines.org', parameter1&parameter2&parameter3); // post to a page
  *  - $fetch_data('http://www.simplemachines.org', parameter1&parameter2&parameter3); // post to a page
  *
  *
- *
  * Get the data
  * Get the data
  *  - $fetch_data->result('body'); // just the page content
  *  - $fetch_data->result('body'); // just the page content
  *  - $fetch_data->result(); // an array of results, body, header, http result codes
  *  - $fetch_data->result(); // an array of results, body, header, http result codes
@@ -27,7 +38,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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
 	*  - will request the page data from a given $url
 	*  - optionally will post data to the page form if post data is supplied
 	*  - 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
 	*  - passed arrays will be converted to a post string joined with &'s
@@ -121,12 +132,12 @@ class curl_fetch_web_data
 		curl_exec($cr);
 		curl_exec($cr);
 
 
 		// Get what was returned
 		// 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
 		// close this request
 		curl_close($cr);
 		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
 		// 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['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
 		// 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'] : '');
 		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)
 	* 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.
 	*  - Call as ->result_raw() for everything.
 	*
 	*
 	* @param type $response_number
 	* @param type $response_number
@@ -223,16 +234,16 @@ class curl_fetch_web_data
 		if (is_array($post_data))
 		if (is_array($post_data))
 		{
 		{
 			$postvars = array();
 			$postvars = array();
-			
+
 			// build the post data, drop ones with leading @'s since those can be used to send files, we don't support that.
 			// 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)
 			foreach ($post_data as $name => $value)
 				$postvars[] = $name . '=' . urlencode($value[0] == '@' ? '' : $value);
 				$postvars[] = $name . '=' . urlencode($value[0] == '@' ? '' : $value);
-			
+
 			return implode('&', $postvars);
 			return implode('&', $postvars);
 		}
 		}
 		else
 		else
 			return $post_data;
 			return $post_data;
-		
+
 	}
 	}
 
 
 	/**
 	/**

+ 1 - 1
Sources/Class-Graphics.php

@@ -13,7 +13,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Class-Package.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbExtra-mysql.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbExtra-postgresql.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbExtra-sqlite.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 2 - 2
Sources/DbPackages-mysql.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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)
 function smf_db_create_query_column($column)
 {
 {
 	global $smcFunc;
 	global $smcFunc;
-	
+
 	// Auto increment is easy here!
 	// Auto increment is easy here!
 	if (!empty($column['auto']))
 	if (!empty($column['auto']))
 	{
 	{

+ 1 - 1
Sources/DbPackages-postgresql.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbPackages-sqlite.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbSearch-mysql.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbSearch-postgresql.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/DbSearch-sqlite.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 35 - 17
Sources/Display.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -143,6 +143,15 @@ function Display()
 		$_SESSION['last_read_topic'] = $topic;
 		$_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 Why isn't this cached?
 	// @todo if we get id_board in this query and cache it, we can save a query on posting
 	// @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.
 	// 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,
 			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
 			' . ($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($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
 		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'] ? '' : '
 			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_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})') . '
 			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}
 		WHERE t.id_topic = {int:current_topic}
 		LIMIT 1',
 		LIMIT 1',
-		array(
-			'current_member' => $user_info['id'],
-			'current_topic' => $topic,
-			'current_board' => $board,
-		)
+			$topic_parameters
 	);
 	);
 	if ($smcFunc['db_num_rows']($request) == 0)
 	if ($smcFunc['db_num_rows']($request) == 0)
 		fatal_lang_error('not_a_topic', false);
 		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.
 	// 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.)
 	// Check if spellchecking is both enabled and actually working. (for quick reply.)
 	$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
 	$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>';
 			$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.
 		// 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...
 	// Information about the current topic...
@@ -962,6 +969,14 @@ function Display()
 				$attachments[$row['id_msg']][] = $row;
 				$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...
 		// What?  It's not like it *couldn't* be only guests in this topic...
 		if (!empty($posters))
 		if (!empty($posters))
 			loadMemberData($posters);
 			loadMemberData($posters);
@@ -970,13 +985,12 @@ function Display()
 				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, body,
 				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, body,
 				smileys_enabled, poster_name, poster_email, approved,
 				smileys_enabled, poster_name, poster_email, approved,
 				id_msg_modified < {int:new_from} AS is_read
 				id_msg_modified < {int:new_from} AS is_read
+				' . (!empty($msg_selects) ? implode(',', $msg_selects) : '') . '
 			FROM {db_prefix}messages
 			FROM {db_prefix}messages
+				' . (!empty($msg_tables) ? implode("\n\t", $msg_tables) : '') . '
 			WHERE id_msg IN ({array_int:message_list})
 			WHERE id_msg IN ({array_int:message_list})
 			ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'),
 			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.
 		// 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_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']);
 	$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.
 	// Wireless shows a "more" if you can do anything special.
 	if (WIRELESS && WIRELESS_PROTOCOL != 'wap')
 	if (WIRELESS && WIRELESS_PROTOCOL != 'wap')
 	{
 	{
@@ -1263,6 +1283,8 @@ function prepareDisplayContext($reset = false)
 	// Is this user the message author?
 	// Is this user the message author?
 	$output['is_message_author'] = $message['id_member'] == $user_info['id'];
 	$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']))
 	if (empty($options['view_newest_first']))
 		$counter++;
 		$counter++;
 	else
 	else
@@ -1408,12 +1430,8 @@ function Download()
 	header('Connection: close');
 	header('Connection: close');
 	header('ETag: ' . $eTag);
 	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.
 	// 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']);
 		unset($_REQUEST['image']);
 
 
 	// Does this have a mime type?
 	// 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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -50,7 +50,7 @@ function DumpDatabase2()
 	@set_time_limit(600);
 	@set_time_limit(600);
 	$time_limit = ini_get('max_execution_time');
 	$time_limit = ini_get('max_execution_time');
 	$start_time = time();
 	$start_time = time();
-	
+
 	// @todo ... fail on not getting the requested memory?
 	// @todo ... fail on not getting the requested memory?
 	setMemoryLimit('256M');
 	setMemoryLimit('256M');
 	$memory_limit = memoryReturnBytes(ini_get('memory_limit')) / 4;
 	$memory_limit = memoryReturnBytes(ini_get('memory_limit')) / 4;
@@ -113,7 +113,7 @@ function DumpDatabase2()
 	$crlf = "\r\n";
 	$crlf = "\r\n";
 
 
 	// SQL Dump Header.
 	// SQL Dump Header.
-	$db_chunks = 
+	$db_chunks =
 		'-- ==========================================================' . $crlf .
 		'-- ==========================================================' . $crlf .
 		'--' . $crlf .
 		'--' . $crlf .
 		'-- Database dump of tables in `' . $db_name . '`' . $crlf .
 		'-- Database dump of tables in `' . $db_name . '`' . $crlf .
@@ -141,7 +141,7 @@ function DumpDatabase2()
 		// Are we dumping the structures?
 		// Are we dumping the structures?
 		if (isset($_REQUEST['struct']))
 		if (isset($_REQUEST['struct']))
 		{
 		{
-			$db_chunks .= 
+			$db_chunks .=
 				$crlf .
 				$crlf .
 				'--' . $crlf .
 				'--' . $crlf .
 				'-- Table structure for table `' . $tableName . '`' . $crlf .
 				'-- Table structure for table `' . $tableName . '`' . $crlf .
@@ -177,7 +177,7 @@ function DumpDatabase2()
 
 
 			if ($first_round)
 			if ($first_round)
 			{
 			{
-				$db_chunks .= 
+				$db_chunks .=
 					$crlf .
 					$crlf .
 					'--' . $crlf .
 					'--' . $crlf .
 					'-- Dumping data in `' . $tableName . '`' . $crlf .
 					'-- Dumping data in `' . $tableName . '`' . $crlf .
@@ -185,7 +185,7 @@ function DumpDatabase2()
 					$crlf;
 					$crlf;
 				$first_round = false;
 				$first_round = false;
 			}
 			}
-			$db_chunks .= 
+			$db_chunks .=
 				$get_rows;
 				$get_rows;
 			$current_used_memory += $smcFunc['strlen']($db_chunks);
 			$current_used_memory += $smcFunc['strlen']($db_chunks);
 
 
@@ -205,11 +205,11 @@ function DumpDatabase2()
 
 
 		// No rows to get - skip it.
 		// No rows to get - skip it.
 		if ($close_table)
 		if ($close_table)
-			$db_backup .= 
+			$db_backup .=
 			'-- --------------------------------------------------------' . $crlf;
 			'-- --------------------------------------------------------' . $crlf;
 	}
 	}
 
 
-	$db_backup .= 
+	$db_backup .=
 		$crlf .
 		$crlf .
 		'-- Done' . $crlf;
 		'-- Done' . $crlf;
 
 

+ 1 - 1
Sources/Errors.php

@@ -9,7 +9,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 4 - 86
Sources/Groups.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -67,91 +67,8 @@ function Groups()
  */
  */
 function GroupList()
 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'];
 	$context['page_title'] = $txt['viewing_groups'];
 
 
 	// Making a list is not hard with this beauty.
 	// Making a list is not hard with this beauty.
@@ -373,6 +290,7 @@ function list_getGroupCount()
  * It allows sorting on several columns.
  * It allows sorting on several columns.
  * It redirects to itself.
  * It redirects to itself.
  * @uses ManageMembergroups template, group_members sub template.
  * @uses ManageMembergroups template, group_members sub template.
+ * @todo: use createList
  */
  */
 function MembergroupMembers()
 function MembergroupMembers()
 {
 {
@@ -914,7 +832,7 @@ function GroupRequests()
 						<option value="reject">' . $txt['mc_groupr_reject'] . '</option>
 						<option value="reject">' . $txt['mc_groupr_reject'] . '</option>
 						<option value="reason">' . $txt['mc_groupr_reject_w_reason'] . '</option>
 						<option value="reason">' . $txt['mc_groupr_reject_w_reason'] . '</option>
 					</select>
 					</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',
 				'align' => 'right',
 			),
 			),
 		),
 		),

+ 2 - 2
Sources/Help.php

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

+ 1 - 1
Sources/Karma.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 87 - 63
Sources/Load.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -175,6 +175,17 @@ function reloadSettings()
 	// Is post moderation alive and well?
 	// Is post moderation alive and well?
 	$modSettings['postmod_active'] = isset($modSettings['admin_features']) ? in_array('pm', explode(',', $modSettings['admin_features'])) : true;
 	$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.
 	// Integration is cool.
 	if (defined('SMF_INTEGRATION_SETTINGS'))
 	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
 	// Allow mods to easily add to the selected member data
 	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables));
 	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables));
-	
+
 	if (!empty($users))
 	if (!empty($users))
 	{
 	{
 		// Load the member's data.
 		// 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 not a user variant, select the default.
 		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
 		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];
 			$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.
 		// Do this to keep things easier in the templates.
 		$context['theme_variant'] = '_' . $context['theme_variant'];
 		$context['theme_variant'] = '_' . $context['theme_variant'];
 		$context['theme_variant_url'] = $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.
 	// Allow overriding the board wide time/number formats.
 	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
 	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
 		$user_info['time_format'] = $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')
 	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;
 	$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.
 	// Compatibility.
 	if (!isset($settings['theme_version']))
 	if (!isset($settings['theme_version']))
 		$modSettings['memberCount'] = $modSettings['totalMembers'];
 		$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');
 	$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
 	// Default JS variables for use in every theme
-	loadLanguage('index');
 	$context['javascript_vars'] = array(
 	$context['javascript_vars'] = array(
 		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
 		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
 		'smf_default_theme_url' => '"' . $settings['default_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']),
 		'ajax_notification_cancel_text' => JavaScriptEscape($txt['modify_cancel']),
 		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
 		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
 	);
 	);
-	
+
 	// Add the JQuery library to the list of files to load.
 	// Add the JQuery library to the list of files to load.
 	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
 	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');
 		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')
 	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
 	else
 		loadJavascriptFile('https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', array(), 'jquery');
 		loadJavascriptFile('https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', array(), 'jquery');
-	
+
 	// Queue our JQuery plugins!
 	// 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.
 	// 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('script.js', array('default_theme' => true), 'smf_scripts');
 	loadJavascriptFile('theme.js', array(), 'theme_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 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())
 	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;
 	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 (!empty($style_sheets))
 	{
 	{
 		if (!is_array($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
  * Add a CSS file for output later
  *
  *
  * @param string $filename
  * @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
  * @param string $id
  */
  */
-function loadCSSFile($filename, $options = array(), $id = '')
+function loadCSSFile($filename, $params = array(), $id = '')
 {
 {
 	global $settings, $context;
 	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?
 	// 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?
 			// 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
 			else
 				$filename = false;
 				$filename = false;
 		}
 		}
 		else
 		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
  * Add a Javascript file for output later
- *
+ 
  * @param string $filename
  * @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
  * @param string $id
  */
  */
-function loadJavascriptFile($filename, $options = array(), $id = '')
+function loadJavascriptFile($filename, $params = array(), $id = '')
 {
 {
 	global $settings, $context;
 	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?
 	// 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
 			else
 				$filename = false;
 				$filename = false;
 		}
 		}
 		else
 		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;
 	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
  * 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 string $javascript
  * @param bool $defer = false, define if the script should load in <head> or before the closing <html> tag
  * @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;
 	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);
 				$data2 = preg_split('~\<br( /)?\>~', $data2);
 
 
 				// Fix the PHP code stuff...
 				// 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);
 					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
 				else
 				else
 					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
 					$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.
  * 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:
  * - It supports:
  *     Turck MMCache: http://turck-mmcache.sourceforge.net/index_old.html#api
  *     Turck MMCache: http://turck-mmcache.sourceforge.net/index_old.html#api
  *     Xcache: http://xcache.lighttpd.net/wiki/XcacheApi
  *     Xcache: http://xcache.lighttpd.net/wiki/XcacheApi

+ 24 - 6
Sources/LogInOut.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -28,7 +28,11 @@ if (!defined('SMF'))
  */
  */
 function Login()
 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.
 	// In wireless?  If so, use the correct sub template.
 	if (WIRELESS)
 	if (WIRELESS)
@@ -115,6 +119,11 @@ function Login2()
 		// Some whitelisting for login_url...
 		// Some whitelisting for login_url...
 		if (empty($_SESSION['login_url']))
 		if (empty($_SESSION['login_url']))
 			redirectexit();
 			redirectexit();
+		elseif (!empty($_SESSION['login_url']) && (strpos('http://', $_SESSION['login_url']) === false && strpos('https://', $_SESSION['login_url']) === false))
+		{
+			unset ($_SESSION['login_url']);
+			redirectexit();
+		}
 		else
 		else
 		{
 		{
 			// Best not to clutter the session data too much...
 			// Best not to clutter the session data too much...
@@ -292,7 +301,7 @@ function Login2()
 		$other_passwords = array();
 		$other_passwords = array();
 
 
 		// None of the below cases will be used most of the time (because the salt is normally set.)
 		// 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.
 			// 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));
 			$other_passwords[] = crypt($_POST['passwrd'], substr($_POST['passwrd'], 0, 2));
@@ -318,10 +327,10 @@ function Login2()
 			$other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5'));
 			$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.
 		// 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/.
 			// 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?
 			// Hmm.. p'raps it's Invision 2 style?
 			$other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
 			$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']));
 			$other_passwords[] = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
 
 
 			// BurningBoard3 style of hashing.
 			// 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.
 			// 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')
 			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']));
 			$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 ;).
 		// Whichever encryption it was using, let's make it use SMF's now ;).
 		if (in_array($user_settings['passwd'], $other_passwords))
 		if (in_array($user_settings['passwd'], $other_passwords))
 		{
 		{
@@ -607,6 +620,11 @@ function Logout($internal = false, $redirect = true)
 	{
 	{
 		if (empty($_SESSION['logout_url']))
 		if (empty($_SESSION['logout_url']))
 			redirectexit('', $context['server']['needs_login_fix']);
 			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
 		else
 		{
 		{
 			$temp = $_SESSION['logout_url'];
 			$temp = $_SESSION['logout_url'];

+ 2 - 1
Sources/Logging.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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_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_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_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 />';
 	', $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?
 	// What tokens are active?

+ 483 - 85
Sources/ManageAttachments.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -78,7 +78,7 @@ function ManageAttachments()
 /**
 /**
  * Allows to show/change attachment settings.
  * Allows to show/change attachment settings.
  * This is the default sub-action of the 'Attachments and Avatars' center.
  * 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
  * @param bool $return_config = false
  * @uses 'attachments' sub template.
  * @uses 'attachments' sub template.
@@ -86,14 +86,40 @@ function ManageAttachments()
 
 
 function ManageAttachmentSettings($return_config = false)
 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.
 	// Perform a test to see if the GD module or ImageMagick are installed.
 	$testImg = get_extension_funcs('gd') || class_exists('Imagick');
 	$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
 	// See if we can find if the server is set up to support the attacment limits
 	$post_max_size = ini_get('post_max_size');
 	$post_max_size = ini_get('post_max_size');
 	$upload_max_filesize = ini_get('upload_max_filesize');
 	$upload_max_filesize = ini_get('upload_max_filesize');
@@ -109,8 +135,14 @@ function ManageAttachmentSettings($return_config = false)
 			array('check', 'attachmentRecodeLineEndings'),
 			array('check', 'attachmentRecodeLineEndings'),
 		'',
 		'',
 			// Directory and size limits.
 			// 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']),
 			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('int', 'attachmentPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
 			array('warning', empty($testPM) ? 'attachment_postsize_warning' : ''),
 			array('warning', empty($testPM) ? 'attachment_postsize_warning' : ''),
 			array('int', 'attachmentSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
 			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('warning', 'attachment_thumb_memory_note'),
 			array('text', 'attachmentThumbWidth', 6),
 			array('text', 'attachmentThumbWidth', 6),
 			array('text', 'attachmentThumbHeight', 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));
 	call_integration_hook('integrate_modify_attachment_settings', array(&$config_vars));
 
 
 	if ($return_config)
 	if ($return_config)
@@ -153,6 +198,38 @@ function ManageAttachmentSettings($return_config = false)
 	{
 	{
 		checkSession();
 		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');
 		call_integration_hook('integrate_save_attachment_settings');
 
 
 		saveDBSettings($config_vars);
 		saveDBSettings($config_vars);
@@ -282,7 +359,7 @@ function BrowseFiles()
 	// Set the options for the list component.
 	// Set the options for the list component.
 	$listOptions = array(
 	$listOptions = array(
 		'id' => 'file_list',
 		'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'],
 		'items_per_page' => $modSettings['defaultMaxMessages'],
 		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
 		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
 		'default_sort_col' => 'name',
 		'default_sort_col' => 'name',
@@ -408,11 +485,8 @@ function BrowseFiles()
 					'value' => $txt['downloads'],
 					'value' => $txt['downloads'],
 				),
 				),
 				'data' => array(
 				'data' => array(
-					'function' => create_function('$rowData','
-						global $txt;
-
-						return comma_format($rowData[\'downloads\']);
-					'),
+					'db' => 'downloads',
+					'comma_format' => true,
 				),
 				),
 				'sort' => array(
 				'sort' => array(
 					'default' => 'a.downloads',
 					'default' => 'a.downloads',
@@ -1008,7 +1082,7 @@ function RepairAttachments()
 	@set_time_limit(600);
 	@set_time_limit(600);
 
 
 	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
 	$_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.
 	// Don't recall the session just in case.
 	if ($_GET['step'] == 0 && $_GET['substep'] == 0)
 	if ($_GET['step'] == 0 && $_GET['substep'] == 0)
@@ -1461,18 +1535,10 @@ function RepairAttachments()
 	// What about files who are not recorded in the database?
 	// What about files who are not recorded in the database?
 	if ($_GET['step'] <= 5)
 	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;
 		$current_check = 0;
 		$max_checks = 500;
 		$max_checks = 500;
@@ -1512,7 +1578,7 @@ function RepairAttachments()
 								);
 								);
 								if ($smcFunc['db_num_rows']($request) == 0)
 								if ($smcFunc['db_num_rows']($request) == 0)
 								{
 								{
-									if ($fix_errors)
+									if ($fix_errors && in_array('files_without_attachment', $to_fix))
 									{
 									{
 										@unlink($attach_dir . '/' . $file);
 										@unlink($attach_dir . '/' . $file);
 									}
 									}
@@ -1525,8 +1591,21 @@ function RepairAttachments()
 								$smcFunc['db_free_result']($request);
 								$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++;
 					$current_check++;
+					$_GET['substep'] = $current_check;
 					if ($current_check - $files_checked >= $max_checks)
 					if ($current_check - $files_checked >= $max_checks)
 						pauseAttachmentMaintenance($to_fix);
 						pauseAttachmentMaintenance($to_fix);
 				}
 				}
@@ -1568,7 +1647,7 @@ function pauseAttachmentMaintenance($to_fix, $max_substep = 0)
 		@apache_reset_timeout();
 		@apache_reset_timeout();
 
 
 	// Have we already used our maximum time?
 	// 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;
 		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'];
 	$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()
 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?
 	// Saving?
 	if (isset($_REQUEST['save']))
 	if (isset($_REQUEST['save']))
 	{
 	{
 		checkSession();
 		checkSession();
 
 
+		$_POST['current_dir'] = (int) $_POST['current_dir'];
 		$new_dirs = array();
 		$new_dirs = array();
 		foreach ($_POST['dirs'] as $id => $path)
 		foreach ($_POST['dirs'] as $id => $path)
 		{
 		{
@@ -1787,33 +1877,132 @@ function ManageAttachmentPaths()
 			if ($id < 1)
 			if ($id < 1)
 				continue;
 				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))
 			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;
 			$new_dirs[$id] = $path;
 		}
 		}
 
 
 		// We need to make sure the current directory is right.
 		// 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']]))
 		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?
 		// Going back to just one path?
 		if (count($new_dirs) == 1)
 		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
 		else
+		{
 			// Save it to the database.
 			// Save it to the database.
-			updateSettings(array(
+			$update = array(
 				'currentAttachmentUploadDir' => $_POST['current_dir'],
 				'currentAttachmentUploadDir' => $_POST['current_dir'],
 				'attachmentUploadDir' => serialize($new_dirs),
 				'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(
 	$listOptions = array(
 		'id' => 'attach_paths',
 		'id' => 'attach_paths',
@@ -1868,13 +2114,13 @@ function ManageAttachmentPaths()
 		'columns' => array(
 		'columns' => array(
 			'current_dir' => array(
 			'current_dir' => array(
 				'header' => array(
 				'header' => array(
-					'value' => $txt['attach_current_dir'],
+					'value' => $txt['attach_current'],
 				),
 				),
 				'data' => array(
 				'data' => array(
 					'function' => create_function('$rowData', '
 					'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(
 			'path' => array(
@@ -1883,9 +2129,9 @@ function ManageAttachmentPaths()
 				),
 				),
 				'data' => array(
 				'data' => array(
 					'function' => create_function('$rowData', '
 					'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(
 			'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" />',
 				'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;',
 				'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');
 	require_once($sourcedir . '/Subs-List.php');
 	createList($listOptions);
 	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.
 	// 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['page_title'] = $txt['attach_path_manage'];
 	$context['sub_template'] = 'attachment_paths';
 	$context['sub_template'] = 'attachment_paths';
 }
 }
@@ -1944,23 +2274,27 @@ function list_getAttachDirs()
 {
 {
 	global $smcFunc, $modSettings, $context, $txt;
 	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']('', '
 	$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',
 		GROUP BY id_folder',
 		array(
 		array(
-			'type_avatar' => 1,
 		)
 		)
 	);
 	);
 
 
 	$expected_files = array();
 	$expected_files = array();
+	$expected_size = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
 	while ($row = $smcFunc['db_fetch_assoc']($request))
+	{
 		$expected_files[$row['id_folder']] = $row['num_attach'];
 		$expected_files[$row['id_folder']] = $row['num_attach'];
+		$expected_size[$row['id_folder']] = $row['size_attach'];
+	}
 	$smcFunc['db_free_result']($request);
 	$smcFunc['db_free_result']($request);
 
 
 	$attachdirs = array();
 	$attachdirs = array();
@@ -1971,15 +2305,33 @@ function list_getAttachDirs()
 			$expected_files[$id] = 0;
 			$expected_files[$id] = 0;
 
 
 		// Check if the directory is doing okay.
 		// 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(
 		$attachdirs[] = array(
 			'id' => $id,
 			'id' => $id,
 			'current' => $id == $modSettings['currentAttachmentUploadDir'],
 			'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,
 			'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;
 	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
  * Checks the status of an attachment directory and returns an array
  *  of the status key, if that status key signifies an error, and
  *  of the status key, if that status key signifies an error, and
- *  the folder size.
+ *  the file count.
  *
  *
  * @param string $dir
  * @param string $dir
  * @param int $expected_files
  * @param int $expected_files
  */
  */
 function attachDirStatus($dir, $expected_files)
 function attachDirStatus($dir, $expected_files)
 {
 {
+	global $sourcedir, $context;
+
 	if (!is_dir($dir))
 	if (!is_dir($dir))
 		return array('does_not_exist', true, '');
 		return array('does_not_exist', true, '');
 	elseif (!is_writable($dir))
 	elseif (!is_writable($dir))
 		return array('not_writable', true, '');
 		return array('not_writable', true, '');
 
 
 	// Everything is okay so far, start to scan through the directory.
 	// Everything is okay so far, start to scan through the directory.
-	$dir_size = 0;
 	$num_files = 0;
 	$num_files = 0;
 	$dir_handle = dir($dir);
 	$dir_handle = dir($dir);
 	while ($file = $dir_handle->read())
 	while ($file = $dir_handle->read())
@@ -2022,20 +2423,17 @@ function attachDirStatus($dir, $expected_files)
 		if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
 		if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
 			continue;
 			continue;
 
 
-		$dir_size += filesize($dir . '/' . $file);
 		$num_files++;
 		$num_files++;
 	}
 	}
 	$dir_handle->close();
 	$dir_handle->close();
 
 
-	$dir_size = round($dir_size / 1024, 2);
-
 	if ($num_files < $expected_files)
 	if ($num_files < $expected_files)
-		return array('files_missing', true, $dir_size);
+		return array('files_missing', true, $num_files);
 	// Empty?
 	// Empty?
 	elseif ($expected_files == 0)
 	elseif ($expected_files == 0)
-		return array('unused', false, $dir_size);
+		return array('unused', false, $num_files);
 	// All good!
 	// All good!
 	else
 	else
-		return array('ok', false, $dir_size);
+		return array('ok', false, $num_files);
 }
 }
 
 

+ 1 - 1
Sources/ManageBans.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/ManageBoards.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 2 - 2
Sources/ManageCalendar.php

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

+ 5 - 5
Sources/ManageErrors.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -148,7 +148,7 @@ function ViewErrorLog()
 				'file' => $row['file'],
 				'file' => $row['file'],
 				'line' => $row['line'],
 				'line' => $row['line'],
 				'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($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']),
 				'search' => base64_encode($row['file']),
 			);
 			);
 		}
 		}
@@ -324,9 +324,9 @@ function deleteErrors()
 /**
 /**
  * View a file specified in $_REQUEST['file'], with php highlighting on it
  * View a file specified in $_REQUEST['file'], with php highlighting on it
  * Preconditions:
  * 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 line number number is specified by $_REQUEST['line']...
  * The function will try to get the 20 lines before and after the specified 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
  * @package SMF
  * @author Simple Machines
  * @author Simple Machines
  *
  *
- * @copyright 2011 Simple Machines
+ * @copyright 2012 Simple Machines
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -147,9 +147,11 @@ function list_getLanguagesList()
 	require_once($sourcedir . '/Class-Package.php');
 	require_once($sourcedir . '/Class-Package.php');
 	$language_list = new xmlArray(fetch_web_data($url), true);
 	$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';
 		$context['smf_error'] = 'no_response';
+	elseif (!$language_list->exists('languages/language'))
+		$context['smf_error'] = 'no_files';
 	else
 	else
 	{
 	{
 		$language_list = $language_list->path('languages[0]');
 		$language_list = $language_list->path('languages[0]');

+ 1 - 1
Sources/ManageMail.php

@@ -9,7 +9,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 58 - 17
Sources/ManageMaintenance.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -82,6 +82,7 @@ function ManageMaintenance()
 			'activities' => array(
 			'activities' => array(
 				'massmove' => 'MaintainMassMoveTopics',
 				'massmove' => 'MaintainMassMoveTopics',
 				'pruneold' => 'MaintainRemoveOldPosts',
 				'pruneold' => 'MaintainRemoveOldPosts',
+				'olddrafts' => 'MaintainRemoveOldDrafts',
 			),
 			),
 		),
 		),
 		'destroy' => array(
 		'destroy' => array(
@@ -163,7 +164,7 @@ function MaintainDatabase()
 	$memory_limit = memoryReturnBytes(ini_get('memory_limit')) / (1024 * 1024);
 	$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
 	// 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!!!)
 	// 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
 	// so when the backup takes 1/5th (just to stay on the safe side) of the memory available
 	$zip_limit = $memory_limit * 1500 / 5;
 	$zip_limit = $memory_limit * 1500 / 5;
 	// Here is more tricky: it depends on many factors, but the main idea is that
 	// 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);
 	$smcFunc['db_free_result']($result);
-	
+
 	if (isset($_GET['done']) && $_GET['done'] == 'recountposts')
 	if (isset($_GET['done']) && $_GET['done'] == 'recountposts')
 		$context['maintenance_finished'] = $txt['maintain_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')
 		if ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8')
 			fatal_lang_error('utf8_already_utf8');
 			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
 		// 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');
 			fatal_lang_error('utf8_cannot_convert_fulltext');
 
 
 		// Grab the character set from the default language file.
 		// Grab the character set from the default language file.
@@ -674,9 +682,9 @@ function ConvertUtf8()
 					foreach ($columns as $column)
 					foreach ($columns as $column)
 					{
 					{
 						$updates_blob .= '
 						$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 .= '
 						$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');
 	isAllowedTo('admin_forum');
 	checkSession('request');
 	checkSession('request');
-	
+
 	// validate the request or the loop
 	// validate the request or the loop
 	if (!isset($_REQUEST['step']))
 	if (!isset($_REQUEST['step']))
 		validateToken('admin-maint');
 		validateToken('admin-maint');
@@ -1807,7 +1815,7 @@ function MaintainPurgeInactiveMembers()
 			$where_vars['is_activated'] = 0;
 			$where_vars['is_activated'] = 0;
 		}
 		}
 		else
 		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.
 		// Need to get *all* groups then work out which (if any) we avoid.
 		$request = $smcFunc['db_query']('', '
 		$request = $smcFunc['db_query']('', '
@@ -1881,6 +1889,39 @@ function MaintainRemoveOldPosts()
 	RemoveOldTopics2();
 	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.
  * Moves topics from one board to another.
  *
  *
@@ -1992,7 +2033,7 @@ function MaintainMassMoveTopics()
 /**
 /**
  * Recalculate all members post counts
  * Recalculate all members post counts
  * it requires the admin_forum permission.
  * it requires the admin_forum permission.
- * 
+ *
  * - recounts all posts for members found in the message table
  * - recounts all posts for members found in the message table
  * - updates the members post count record in the members talbe
  * - updates the members post count record in the members talbe
  * - honors the boards post count flag
  * - honors the boards post count flag
@@ -2017,7 +2058,7 @@ function MaintainRecountPosts()
 	$context['continue_countdown'] = 3;
 	$context['continue_countdown'] = 3;
 	$context['continue_get_data'] = '';
 	$context['continue_get_data'] = '';
 	$context['sub_template'] = 'not_done';
 	$context['sub_template'] = 'not_done';
-	
+
 	// init
 	// init
 	$increment = 200;
 	$increment = 200;
 	$_REQUEST['start'] = !isset($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
 	$_REQUEST['start'] = !isset($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
@@ -2029,7 +2070,7 @@ function MaintainRecountPosts()
 	if (!isset($_SESSION['total_members']))
 	if (!isset($_SESSION['total_members']))
 	{
 	{
 		validateToken('admin-maint');
 		validateToken('admin-maint');
-		
+
 		$request = $smcFunc['db_query']('', '
 		$request = $smcFunc['db_query']('', '
 			SELECT COUNT(DISTINCT m.id_member)
 			SELECT COUNT(DISTINCT m.id_member)
 			FROM ({db_prefix}messages AS m, {db_prefix}boards AS b)
 			FROM ({db_prefix}messages AS m, {db_prefix}boards AS b)
@@ -2087,7 +2128,7 @@ function MaintainRecountPosts()
 		$_REQUEST['start'] += $increment;
 		$_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_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']);
 		$context['continue_percent'] = round(100 * $_REQUEST['start'] / $_SESSION['total_members']);
-		
+
 		createToken('admin-recountposts');
 		createToken('admin-recountposts');
 		$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-recountposts_token_var'] . '" value="' . $context['admin-recountposts_token'] . '" />';
 		$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();
 			apache_reset_timeout();
 		return;
 		return;
 	}
 	}
-	
+
 	// final steps ... made more difficult since we don't yet support sub-selects on joins
 	// 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
 	// place all members who have posts in the message table in a temp table
 	$createTemporary = $smcFunc['db_query']('', '
 	$createTemporary = $smcFunc['db_query']('', '
@@ -2103,7 +2144,7 @@ function MaintainRecountPosts()
 			id_member mediumint(8) unsigned NOT NULL default {string:string_zero},
 			id_member mediumint(8) unsigned NOT NULL default {string:string_zero},
 			PRIMARY KEY (id_member)
 			PRIMARY KEY (id_member)
 		)
 		)
-		SELECT m.id_member 
+		SELECT m.id_member
 		FROM ({db_prefix}messages AS m,{db_prefix}boards AS b)
 		FROM ({db_prefix}messages AS m,{db_prefix}boards AS b)
 		WHERE m.id_member != {int:zero}
 		WHERE m.id_member != {int:zero}
 			AND b.count_posts = {int:zero}
 			AND b.count_posts = {int:zero}
@@ -2117,7 +2158,7 @@ function MaintainRecountPosts()
 		)
 		)
 	) !== false;
 	) !== 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
 		// 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']('', '
 		$request = $smcFunc['db_query']('', '
@@ -2125,7 +2166,7 @@ function MaintainRecountPosts()
 			FROM {db_prefix}members AS mem
 			FROM {db_prefix}members AS mem
 			LEFT OUTER JOIN {db_prefix}tmp_maint_recountposts AS res
 			LEFT OUTER JOIN {db_prefix}tmp_maint_recountposts AS res
 			ON res.id_member = mem.id_member
 			ON res.id_member = mem.id_member
-			WHERE res.id_member IS null 
+			WHERE res.id_member IS null
 				AND mem.posts != {int:zero}',
 				AND mem.posts != {int:zero}',
 			array(
 			array(
 				'zero' => 0,
 				'zero' => 0,
@@ -2147,7 +2188,7 @@ function MaintainRecountPosts()
 		}
 		}
 		$smcFunc['db_free_result']($request);
 		$smcFunc['db_free_result']($request);
 	}
 	}
-	
+
 	// all done
 	// all done
 	unset($_SESSION['total_members']);
 	unset($_SESSION['total_members']);
 	$context['maintenance_finished'] = $txt['maintain_recountposts'];
 	$context['maintenance_finished'] = $txt['maintain_recountposts'];

+ 1 - 5
Sources/ManageMembergroups.php

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

+ 16 - 18
Sources/ManageMembers.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -369,7 +369,10 @@ function ViewMemberlist()
 				// Replace the wildcard characters ('*' and '?') into MySQL ones.
 				// Replace the wildcard characters ('*' and '?') into MySQL ones.
 				$parameter = strtolower(strtr($smcFunc['htmlspecialchars']($search_params[$param_name], ENT_QUOTES), array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_')));
 				$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 . '%';
 				$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'))
 	if (!allowedTo('profile_remove_any'))
 		unset($listOptions['cols']['check'], $listOptions['form'], $listOptions['additional_rows']);
 		unset($listOptions['cols']['check'], $listOptions['form'], $listOptions['additional_rows']);
 
 
-	call_integration_hook('integrate_view_members_list', array(&$listOptions));
-
 	require_once($sourcedir . '/Subs-List.php');
 	require_once($sourcedir . '/Subs-List.php');
 	createList($listOptions);
 	createList($listOptions);
 
 
@@ -963,15 +964,13 @@ function MembersAwaitingActivation()
 			array(
 			array(
 				'position' => 'below_table_data',
 				'position' => 'below_table_data',
 				'value' => '
 				'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>
 			</select>
 			<noscript><input type="submit" value="' . $txt['go'] . '" name="filter" class="button_submit" /></noscript>';
 			<noscript><input type="submit" value="' . $txt['go'] . '" name="filter" class="button_submit" /></noscript>';
 		$listOptions['additional_rows'][] = array(
 		$listOptions['additional_rows'][] = array(
-			'position' => 'above_column_headers',
+			'position' => 'top_of_list',
 			'value' => $filterOptions,
 			'value' => $filterOptions,
-			'style' => 'text-align: center;',
+			'class' => 'righttext',
 		);
 		);
 	}
 	}
 
 
@@ -1010,8 +1009,7 @@ function MembersAwaitingActivation()
 		$listOptions['additional_rows'][] = array(
 		$listOptions['additional_rows'][] = array(
 			'position' => 'above_column_headers',
 			'position' => 'above_column_headers',
 			'value' => '<strong>' . $txt['admin_browse_filter_show'] . ':</strong> ' . $context['available_filters'][0]['desc'],
 			'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.
 	// Now that we have all the options, create the list.

+ 5 - 4
Sources/ManageNews.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -151,7 +151,7 @@ function EditNews()
 					'function' => create_function('$news', '
 					'function' => create_function('$news', '
 
 
 						if (is_numeric($news[\'id\']))
 						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 />
 							<br />
 							<div class="floatleft" id="preview_\' . $news[\'id\'] . \'"></div>\';
 							<div class="floatleft" id="preview_\' . $news[\'id\'] . \'"></div>\';
 						else
 						else
@@ -201,7 +201,7 @@ function EditNews()
 				<span id="moreNewsItems_link" class="floatleft" style="display: none;">
 				<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>
 					<a class="button_link" href="javascript:void(0);" onclick="addNewsItem(); return false;">' . $txt['editnews_clickadd'] . '</a>
 				</span>
 				</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" />',
 				<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(\'list_news_lists_last\').style.display = "none";
 					document.getElementById("moreNewsItems_link").style.display = "";
 					document.getElementById("moreNewsItems_link").style.display = "";
 					var last_preview = 0;
 					var last_preview = 0;
-					
+
 					$(document).ready(function () {
 					$(document).ready(function () {
 						$("div[id ^= \'preview_\']").each(function () {
 						$("div[id ^= \'preview_\']").each(function () {
 							var preview_id = $(this).attr(\'id\').split(\'_\')[1];
 							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.
 	// 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;
 	$num_at_once = empty($modSettings['mail_queue']) ? 60 : 1000;
 
 
 	// If by PM's I suggest we half the above number.
 	// If by PM's I suggest we half the above number.

+ 6 - 9
Sources/ManagePaid.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -686,7 +686,7 @@ function ViewSubscribedUsers()
 			'payments_pending' => array(
 			'payments_pending' => array(
 				'header' => array(
 				'header' => array(
 					'value' => $txt['paid_payments_pending'],
 					'value' => $txt['paid_payments_pending'],
-					'style' => 'text-align: left; width: 10%;',
+					'style' => 'text-align: left; width: 15%;',
 				),
 				),
 				'data' => array(
 				'data' => array(
 					'db_htmlsafe' => 'pending',
 					'db_htmlsafe' => 'pending',
@@ -758,9 +758,7 @@ function ViewSubscribedUsers()
 			array(
 			array(
 				'position' => 'below_table_data',
 				'position' => 'below_table_data',
 				'value' => '
 				'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="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" />
 					<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(
 			array(
 				'position' => 'top_of_list',
 				'position' => 'top_of_list',
 				'value' => '
 				'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>
 					</div>
 				',
 				',
 			),
 			),

+ 7 - 1
Sources/ManagePermissions.php

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

+ 7 - 3
Sources/ManagePosts.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -45,7 +45,7 @@ function ManagePostSettings()
 
 
 	$context['page_title'] = $txt['manageposts_title'];
 	$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(
 	$context[$context['admin_menu_name']]['tab_data'] = array(
 		'title' => $txt['manageposts_title'],
 		'title' => $txt['manageposts_title'],
 		'help' => 'posts_and_topics',
 		'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'));
 				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 we're changing the post preview length let's check its valid
 		if (!empty($_POST['preview_characters']))
 		if (!empty($_POST['preview_characters']))
 			$_POST['preview_characters'] = (int) min(max(0, $_POST['preview_characters']), 512);
 			$_POST['preview_characters'] = (int) min(max(0, $_POST['preview_characters']), 512);
@@ -266,12 +266,16 @@ function ModifyBBCSettings($return_config = false)
 	$config_vars = array(
 	$config_vars = array(
 			// Main tweaks
 			// Main tweaks
 			array('check', 'enableBBC'),
 			array('check', 'enableBBC'),
+			array('check', 'enableBBC', 0, 'onchange' => 'toggleBBCDisabled(\'disabledBBC\', !this.checked);'),
 			array('check', 'enablePostHTML'),
 			array('check', 'enablePostHTML'),
 			array('check', 'autoLinkUrls'),
 			array('check', 'autoLinkUrls'),
 		'',
 		'',
 			array('bbc', 'disabledBBC'),
 			array('bbc', 'disabledBBC'),
 	);
 	);
 
 
+	$context['settings_post_javascript'] = '
+		toggleBBCDisabled(\'disabledBBC\', ' . (empty($modSettings['enableBBC']) ? 'true' : 'false') . ');';
+
 	call_integration_hook('integrate_modify_bbc_settings', array(&$config_vars));
 	call_integration_hook('integrate_modify_bbc_settings', array(&$config_vars));
 
 
 	if ($return_config)
 	if ($return_config)

+ 1 - 1
Sources/ManageRegistration.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/ManageScheduledTasks.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 54 - 49
Sources/ManageSearch.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -216,52 +216,7 @@ function EditSearchMethod()
 
 
 	// Detect whether a fulltext index is set.
 	// Detect whether a fulltext index is set.
 	if ($context['supports_fulltext'])
 	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')
 	if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'createfulltext')
 	{
 	{
@@ -542,7 +497,7 @@ function CreateMessageIndex()
 		);
 		);
 		$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
 		$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
 		$context['step'] = isset($_REQUEST['step']) ? (int) $_REQUEST['step'] : 0;
 		$context['step'] = isset($_REQUEST['step']) ? (int) $_REQUEST['step'] : 0;
-		
+
 		// admin timeouts are painful when building these long indexes
 		// admin timeouts are painful when building these long indexes
 		if ($_SESSION['admin_time'] + 3300 < time() && $context['step'] >= 1)
 		if ($_SESSION['admin_time'] + 3300 < time() && $context['step'] >= 1)
 			$_SESSION['admin_time'] = time();
 			$_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;
 			$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.
 	// Step 3: remove words not distinctive enough.
 	if ($context['step'] === 3)
 	if ($context['step'] === 3)
 	{
 	{
@@ -789,3 +744,53 @@ function loadSearchAPIs()
 
 
 	return $apis;
 	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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -459,7 +459,7 @@ function EditSpider()
 
 
 /**
 /**
  * Do we think the current user is a spider?
  * Do we think the current user is a spider?
- * 
+ *
  * @todo Should this not be... you know... in a different file?
  * @todo Should this not be... you know... in a different file?
  * @return int
  * @return int
  */
  */
@@ -538,7 +538,7 @@ function SpiderCheck()
 
 
 /**
 /**
  * Log the spider presence online.
  * Log the spider presence online.
- * 
+ *
  * @todo Different file?
  * @todo Different file?
  */
  */
 function logSpider()
 function logSpider()
@@ -798,7 +798,7 @@ function SpiderLogs()
 
 
 /**
 /**
  * Callback function for createList()
  * Callback function for createList()
- * 
+ *
  * @param int $start
  * @param int $start
  * @param int $items_per_page
  * @param int $items_per_page
  * @param string $sort
  * @param string $sort

+ 20 - 39
Sources/ManageServer.php

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

+ 137 - 68
Sources/ManageSettings.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -225,6 +225,38 @@ function ModifyCoreFeatures($return_config = false)
 					return array();
 					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 = karma.
 		'k' => array(
 		'k' => array(
 			'url' => 'action=admin;area=featuresettings;sa=karma',
 			'url' => 'action=admin;area=featuresettings;sa=karma',
@@ -357,15 +389,15 @@ function ModifyCoreFeatures($return_config = false)
 	{
 	{
 		checkSession();
 		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());
 		$setting_changes = array('admin_features' => array());
 
 
@@ -478,7 +510,6 @@ function ModifyBasicSettings($return_config = false)
 		'',
 		'',
 			// Number formatting, timezones.
 			// Number formatting, timezones.
 			array('text', 'time_format'),
 			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']),
 			array('float', 'time_offset', 'subtext' => $txt['setting_time_offset_note'], 6, 'postinput' => $txt['hours']),
 			'default_timezone' => array('select', 'default_timezone', array()),
 			'default_timezone' => array('select', 'default_timezone', array()),
 		'',
 		'',
@@ -492,7 +523,7 @@ function ModifyBasicSettings($return_config = false)
 		'',
 		'',
 			// Option-ish things... miscellaneous sorta.
 			// Option-ish things... miscellaneous sorta.
 			array('check', 'allow_disableAnnounce'),
 			array('check', 'allow_disableAnnounce'),
-			array('check', 'disallow_sendBody'),	
+			array('check', 'disallow_sendBody'),
 	);
 	);
 
 
 	// Get all the time zones.
 	// Get all the time zones.
@@ -562,6 +593,7 @@ function ModifyGeneralSecuritySettings($return_config = false)
 		'',
 		'',
 			// Password strength.
 			// Password strength.
 			array('select', 'password_strength', array($txt['setting_password_strength_low'], $txt['setting_password_strength_medium'], $txt['setting_password_strength_high'])),
 			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?
 			// Reporting of personal messages?
 			array('check', 'enableReportPM'),
 			array('check', 'enableReportPM'),
@@ -612,8 +644,8 @@ function ModifyLayoutSettings($return_config = false)
 			array('check', 'enableVBStyleLogin'),
 			array('check', 'enableVBStyleLogin'),
 		'',
 		'',
 			// Automagic image resizing.
 			// 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.
 			// This is like debugging sorta.
 			array('check', 'timeLoadPageEnable'),
 			array('check', 'timeLoadPageEnable'),
@@ -1737,7 +1769,7 @@ function EditCustomProfiles()
 		// Regex you say?  Do a very basic test to see if the pattern is valid
 		// 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)
 		if (!empty($_POST['regex']) && @preg_match($_POST['regex'], 'dummy') === false)
 			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error');
 			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error');
-			
+
 		$_POST['field_name'] = $smcFunc['htmlspecialchars']($_POST['field_name']);
 		$_POST['field_name'] = $smcFunc['htmlspecialchars']($_POST['field_name']);
 		$_POST['field_desc'] = $smcFunc['htmlspecialchars']($_POST['field_desc']);
 		$_POST['field_desc'] = $smcFunc['htmlspecialchars']($_POST['field_desc']);
 
 
@@ -2168,34 +2200,41 @@ function list_integration_hooks()
 	global $sourcedir, $scripturl, $context, $txt, $modSettings, $settings;
 	global $sourcedir, $scripturl, $context, $txt, $modSettings, $settings;
 
 
 	$context['filter'] = '';
 	$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'];
 		$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(
 	$list_options = array(
 		'id' => 'list_integration_hooks',
 		'id' => 'list_integration_hooks',
@@ -2228,7 +2267,14 @@ function list_integration_hooks()
 					'value' => $txt['hooks_field_function_name'],
 					'value' => $txt['hooks_field_function_name'],
 				),
 				),
 				'data' => array(
 				'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(
 				'sort' =>  array(
 					'default' => 'function_name',
 					'default' => 'function_name',
@@ -2259,7 +2305,7 @@ function list_integration_hooks()
 						$change_status = array(\'before\' => \'\', \'after\' => \'\');
 						$change_status = array(\'before\' => \'\', \'after\' => \'\');
 						if ($data[\'can_be_disabled\'] && $data[\'status\'] != \'deny\')
 						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>\';
 							$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\'];
 						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',
 					'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(
 		'additional_rows' => array(
 			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');
 	require_once($sourcedir . '/Subs-List.php');
 	createList($list_options);
 	createList($list_options);
 
 
@@ -2340,7 +2393,7 @@ function get_files_recursive($dir_path)
 
 
 function get_integration_hooks_data($start, $per_page, $sort)
 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 = $temp_hooks = get_integration_hooks();
 	$hooks_data = $temp_data = $hook_status = array();
 	$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)
 					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')
 						if (substr($hook, -8) === '_include')
 						{
 						{
 							$hook_status[$hook][$function]['exists'] = file_exists(strtr(trim($function), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
 							$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);
 							$temp_data['include'][basename($function)] = array('hook' => $hook, 'function' => $function);
 							unset($temp_hooks[$hook][$function_o]);
 							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)
 							// 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;
 							$temp_data['function'][$file['name']][] = $function_o;
 							unset($temp_hooks[$hook][$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);
 				$function = str_replace(']', '', $function);
 				$hook_exists = !empty($hook_status[$hook][$function]['exists']);
 				$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');
 				$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];
 				$sort[] = $$sort_options[0];
+
+				if (strpos($function, '::') !== false)
+				{
+					$function = explode('::', $function);
+					$function = $function[1];
+				}
+				$exploded = explode(':', $function);
+
 				$temp_data[] = array(
 				$temp_data[] = array(
 					'id' => 'hookid_' . $id++,
 					'id' => 'hookid_' . $id++,
 					'hook_name' => $hook,
 					'hook_name' => $hook,
 					'function_name' => $function,
 					'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'] : ''),
 					'file_name' => (isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ''),
 					'hook_exists' => $hook_exists,
 					'hook_exists' => $hook_exists,
 					'status' => $hook_exists ? ($enabled ? 'allow' : 'moderate') : 'deny',
 					'status' => $hook_exists ? ($enabled ? 'allow' : 'moderate') : 'deny',
 					'img_text' => $txt['hooks_' . ($hook_exists ? ($enabled ? 'active' : 'disabled') : 'missing')],
 					'img_text' => $txt['hooks_' . ($hook_exists ? ($enabled ? 'active' : 'disabled') : 'missing')],
 					'enabled' => $enabled,
 					'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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -207,7 +207,7 @@ function EditSmileySets()
 			$set_paths = explode(',', $modSettings['smiley_sets_known']);
 			$set_paths = explode(',', $modSettings['smiley_sets_known']);
 			$set_names = explode("\n", $modSettings['smiley_sets_names']);
 			$set_names = explode("\n", $modSettings['smiley_sets_names']);
 			foreach ($_POST['smiley_set'] as $id => $val)
 			foreach ($_POST['smiley_set'] as $id => $val)
-			{	
+			{
 				if (isset($set_paths[$id], $set_names[$id]) && !empty($id))
 				if (isset($set_paths[$id], $set_names[$id]) && !empty($id))
 					unset($set_paths[$id], $set_names[$id]);
 					unset($set_paths[$id], $set_names[$id]);
 			}
 			}

+ 23 - 18
Sources/Memberlist.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -150,7 +150,7 @@ function Memberlist()
 			),
 			),
 		)
 		)
 	);
 	);
-	
+
 	$context['colspan'] = 0;
 	$context['colspan'] = 0;
 	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
 	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
 	foreach ($context['columns'] as $key => $column)
 	foreach ($context['columns'] as $key => $column)
@@ -175,15 +175,15 @@ function Memberlist()
 
 
 	$context['can_send_pm'] = allowedTo('pm_send');
 	$context['can_send_pm'] = allowedTo('pm_send');
 	$context['can_send_email'] = allowedTo('send_email_to_members');
 	$context['can_send_email'] = allowedTo('send_email_to_members');
-	
+
 	// Build the memberlist button array.
 	// Build the memberlist button array.
 	$context['memberlist_buttons'] = 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),
 		'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'),
 		'mlist_search' => array('text' => 'mlist_search', 'image' => 'mlist.png', 'lang' => true, 'url' => $scripturl . '?action=mlist' . ';sa=search'),
 	);
 	);
-	
+
 	// Allow mods to add additional buttons here
 	// Allow mods to add additional buttons here
-	call_integration_hook('integrate_memberlist_buttons');	
+	call_integration_hook('integrate_memberlist_buttons');
 
 
 	// Jump to the sub action.
 	// Jump to the sub action.
 	if (isset($subActions[$context['listing_by']]))
 	if (isset($subActions[$context['listing_by']]))
@@ -406,6 +406,9 @@ function MLSearch()
 {
 {
 	global $txt, $scripturl, $context, $user_info, $modSettings, $smcFunc;
 	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?
 	// Can they search custom fields?
 	$request = $smcFunc['db_query']('', '
 	$request = $smcFunc['db_query']('', '
 		SELECT col_name, field_name, field_desc
 		SELECT col_name, field_name, field_desc
@@ -443,7 +446,7 @@ function MLSearch()
 		// No fields?  Use default...
 		// No fields?  Use default...
 		if (empty($_POST['fields']))
 		if (empty($_POST['fields']))
 			$_POST['fields'] = array('name');
 			$_POST['fields'] = array('name');
-			
+
 		// Set defaults for how the results are sorted
 		// Set defaults for how the results are sorted
 		if (!isset($_REQUEST['sort']) || !isset($context['columns'][$_REQUEST['sort']]))
 		if (!isset($_REQUEST['sort']) || !isset($context['columns'][$_REQUEST['sort']]))
 			$_REQUEST['sort'] = 'real_name';
 			$_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'])))
 			if ((!isset($_REQUEST['desc']) && $col == $_REQUEST['sort']) || ($col != $_REQUEST['sort'] && !empty($column_details['default_sort_rev'])))
 				$context['columns'][$col]['href'] .= ';desc';
 				$context['columns'][$col]['href'] .= ';desc';
-				
+
 			if (isset($_POST['search']) && isset($_POST['fields']))
 			if (isset($_POST['search']) && isset($_POST['fields']))
 				$context['columns'][$col]['href'] .= ';search=' . $_POST['search'] . ';fields=' . implode(',', $_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]['link'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>';
 			$context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col;
 			$context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col;
 		}
 		}
-	
+
 		// set up some things for use in the template
 		// 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'];
 		$context['sort_by'] = $_REQUEST['sort'];
 
 
 		$query_parameters = array(
 		$query_parameters = array(
@@ -500,9 +501,13 @@ function MLSearch()
 		else
 		else
 			$condition = '';
 			$condition = '';
 
 
+		if ($smcFunc['db_case_sensitive'])
+			foreach ($fields as $key => $field)
+				$fields[$key] = 'LOWER(' . $field . ')';
+
 		$customJoin = array();
 		$customJoin = array();
 		$customCount = 10;
 		$customCount = 10;
-		
+
 		// Any custom fields to search for - these being tricky?
 		// Any custom fields to search for - these being tricky?
 		foreach ($_POST['fields'] as $field)
 		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']('', '
 		$request = $smcFunc['db_query']('', '
 			SELECT COUNT(*)
 			SELECT COUNT(*)
@@ -574,7 +579,7 @@ function MLSearch()
 		'url' => $scripturl . '?action=mlist;sa=search',
 		'url' => $scripturl . '?action=mlist;sa=search',
 		'name' => &$context['page_title']
 		'name' => &$context['page_title']
 	);
 	);
-	
+
 	// Highlight the correct button, too!
 	// Highlight the correct button, too!
 	unset($context['memberlist_buttons']['view_all_members']['active']);
 	unset($context['memberlist_buttons']['view_all_members']['active']);
 	$context['memberlist_buttons']['mlist_search']['active'] = true;
 	$context['memberlist_buttons']['mlist_search']['active'] = true;
@@ -598,12 +603,12 @@ function printMemberListRows($request)
 		array(
 		array(
 		)
 		)
 	);
 	);
-	list ($MOST_POSTS) = $smcFunc['db_fetch_row']($result);
+	list ($most_posts) = $smcFunc['db_fetch_row']($result);
 	$smcFunc['db_free_result']($result);
 	$smcFunc['db_free_result']($result);
 
 
 	// Avoid division by zero...
 	// Avoid division by zero...
-	if ($MOST_POSTS == 0)
-		$MOST_POSTS = 1;
+	if ($most_posts == 0)
+		$most_posts = 1;
 
 
 	$members = array();
 	$members = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
 	while ($row = $smcFunc['db_fetch_assoc']($request))
@@ -619,7 +624,7 @@ function printMemberListRows($request)
 			continue;
 			continue;
 
 
 		$context['members'][$member] = $memberContext[$member];
 		$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']);
 		$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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -113,7 +113,7 @@ function MessageIndex()
 		foreach ($board_info['moderators'] as $mod)
 		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['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.
 	// 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,
 				' . ($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,
 				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.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,
 				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_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,
 				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']) ? '
 				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(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
 			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 ml ON (ml.id_msg = t.id_last_msg)
 				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_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 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}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_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'] ? '' : '
 			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}') . ')') . '
 				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')) . '
 			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';
 					$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.
 			// 'Print' the topic info.
 			$context['topics'][$row['id_topic']] = array(
 			$context['topics'][$row['id_topic']] = array(
 				'id' => $row['id_topic'],
 				'id' => $row['id_topic'],
@@ -530,6 +550,13 @@ function MessageIndex()
 				'approved' => $row['approved'],
 				'approved' => $row['approved'],
 				'unapproved_posts' => $row['unapproved_posts'],
 				'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']]);
 			determineTopicClass($context['topics'][$row['id_topic']]);
 		}
 		}
@@ -609,15 +636,6 @@ function MessageIndex()
 				'use_permissions' => true,
 				'use_permissions' => true,
 				'selected_board' => empty($_SESSION['move_to_topic']) ? null : $_SESSION['move_to_topic'],
 				'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.
 			// With no other boards to see, it's useless to move.
 			if (empty($context['move_to_boards']))
 			if (empty($context['move_to_boards']))

+ 3 - 18
Sources/ModerationCenter.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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...
 	// Make sure the administrator has a valid session...
 	validateSession('moderate');
 	validateSession('moderate');
 
 
@@ -225,7 +210,7 @@ function ModerationHome()
 	global $txt, $context, $scripturl, $modSettings, $user_info, $user_settings;
 	global $txt, $context, $scripturl, $modSettings, $user_info, $user_settings;
 
 
 	loadTemplate('ModerationCenter');
 	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['page_title'] = $txt['moderation_center'];
 	$context['sub_template'] = 'moderation_center';
 	$context['sub_template'] = 'moderation_center';
@@ -2164,5 +2149,5 @@ function ModEndSession()
 		if (strpos($key, '-mod') !== false)
 		if (strpos($key, '-mod') !== false)
 			unset($_SESSION['token'][$key]);
 			unset($_SESSION['token'][$key]);
 
 
-	redirectexit('?action=moderate');
+	redirectexit('action=moderate');
 }
 }

+ 1 - 1
Sources/Modlog.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/MoveTopic.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 4 - 4
Sources/News.php

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

+ 1 - 1
Sources/Notify.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/PackageGet.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 61 - 34
Sources/Packages.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -625,7 +625,7 @@ function PackageInstallTest()
 				// Is the action already stated?
 				// Is the action already stated?
 				$theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
 				$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'];
 				$action['unparsed_destination'] = $action['unparsed_filename'];
-				
+
 				// If it's not auto do we think we have something we can act upon?
 				// 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')))
 				if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
 					$theme_action = '';
 					$theme_action = '';
@@ -645,7 +645,12 @@ function PackageInstallTest()
 		if (empty($thisAction))
 		if (empty($thisAction))
 			continue;
 			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;
 			$context['has_failure'] = true;
 
 
@@ -689,7 +694,7 @@ function PackageInstallTest()
 				if (isset($theme_data['theme_dir']) && $id != 1)
 				if (isset($theme_data['theme_dir']) && $id != 1)
 				{
 				{
 					$real_path = $theme_data['theme_dir'] . $path;
 					$real_path = $theme_data['theme_dir'] . $path;
-					
+
 					// Confirm that we don't already have this dealt with by another entry.
 					// Confirm that we don't already have this dealt with by another entry.
 					if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes']))
 					if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes']))
 					{
 					{
@@ -701,7 +706,7 @@ function PackageInstallTest()
 								$temp = dirname($temp);
 								$temp = dirname($temp);
 							$chmod_files[] = $temp;
 							$chmod_files[] = $temp;
 						}
 						}
-						
+
 						if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path))))
 						if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path))))
 							$chmod_files[] = $real_path;
 							$chmod_files[] = $real_path;
 
 
@@ -1011,9 +1016,9 @@ function PackageInstall()
 			elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function']))
 			elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function']))
 			{
 			{
 				if ($action['reverse'])
 				if ($action['reverse'])
-					remove_integration_function($action['hook'], $action['function']);
+					remove_integration_function($action['hook'], $action['function'], $action['include_file']);
 				else
 				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.
 			// Only do the database changes on uninstall if requested.
 			elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes'])))
 			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['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;
 	$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');
 	require_once($sourcedir . '/Subs-List.php');
 
 
 	foreach ($context['modification_types'] as $type)
 	foreach ($context['modification_types'] as $type)
@@ -1377,13 +1382,13 @@ function PackageBrowse()
 				'function' => 'list_getPackages',
 				'function' => 'list_getPackages',
 				'params' => array('type' => $type, 'installed' => $installed),
 				'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,
 			'default_sort_col' => 'id' . $type,
 			'columns' => array(
 			'columns' => array(
 				'id' . $type => array(
 				'id' . $type => array(
 					'header' => array(
 					'header' => array(
 						'value' => $txt['package_id'],
 						'value' => $txt['package_id'],
-						'style' => 'width: 32px;',
+						'style' => 'width: 40px;',
 					),
 					),
 					'data' => array(
 					'data' => array(
 						'function' => create_function('$package_md5', '
 						'function' => create_function('$package_md5', '
@@ -1391,11 +1396,6 @@ function PackageBrowse()
 
 
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
 								return $context[\'available_' . $type . '\'][$package_md5][\'sort_id\'];
 								return $context[\'available_' . $type . '\'][$package_md5][\'sort_id\'];
-							return $context[\'sort_id\'];
-							if (empty($packageCounter))
-								$packageCounter = 1;
-
-							return $packageCounter++ . \'.\';
 						'),
 						'),
 					),
 					),
 					'sort' => array(
 					'sort' => array(
@@ -1413,8 +1413,8 @@ function PackageBrowse()
 							global $context;
 							global $context;
 
 
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
-								return $context[\'available_' . $type . '\'][$package_md5][\'name\'];'
-						),
+								return $context[\'available_' . $type . '\'][$package_md5][\'name\'];
+						'),
 					),
 					),
 					'sort' => array(
 					'sort' => array(
 						'default' => 'name',
 						'default' => 'name',
@@ -1431,8 +1431,8 @@ function PackageBrowse()
 							global $context;
 							global $context;
 
 
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
 							if (isset($context[\'available_' . $type . '\'][$package_md5]))
-								return $context[\'available_' . $type . '\'][$package_md5][\'version\'];'
-						),
+								return $context[\'available_' . $type . '\'][$package_md5][\'version\'];
+						'),
 					),
 					),
 					'sort' => array(
 					'sort' => array(
 						'default' => 'version',
 						'default' => 'version',
@@ -1472,8 +1472,8 @@ function PackageBrowse()
 
 
 							return $return . \'
 							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=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;',
 						'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 ' => ''));
 	$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 ;)
 	// 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']))
 		if (($_GET['version_emulate'] === 0 || $_GET['version_emulate'] === $forum_version) && isset($_SESSION['version_emulate']))
 			unset($_SESSION['version_emulate']);
 			unset($_SESSION['version_emulate']);
@@ -1549,10 +1553,11 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 
 
 	if ($installed)
 	if ($installed)
 	{
 	{
+		$sort_id = 1;
 		foreach ($instmods as $installed_mod)
 		foreach ($instmods as $installed_mod)
 		{
 		{
-			$packages['modification'][] = $installed_mod['package_id'];
 			$context['available_modification'][$installed_mod['package_id']] = array(
 			$context['available_modification'][$installed_mod['package_id']] = array(
+				'sort_id' => $sort_id++,
 				'can_uninstall' => true,
 				'can_uninstall' => true,
 				'name' => $installed_mod['name'],
 				'name' => $installed_mod['name'],
 				'filename' => $installed_mod['filename'],
 				'filename' => $installed_mod['filename'],
@@ -1562,16 +1567,22 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 				'is_current' => true,
 				'is_current' => true,
 			);
 			);
 		}
 		}
-		return $packages['modification'];
 	}
 	}
 
 
 	if (empty($packages))
 	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'))
 	if ($dir = @opendir($boarddir . '/Packages'))
 	{
 	{
 		$dirs = array();
 		$dirs = array();
-		$sort_id = 1;
+		$sort_id = array(
+			'mod' => 1,
+			'modification' => 1,
+			'avatar' => 1,
+			'language' => 1,
+			'unknown' => 1,
+		);
 		while ($package = readdir($dir))
 		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'))
 			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['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_installed'] = isset($installed_mods[$packageInfo['id']]);
 				$packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']);
 				$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']);
 				$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']))
 					if ($packageInfo['can_install'] === false && $install->exists('@for') && empty($_SESSION['version_emulate']))
 					{
 					{
 						$reset = true;
 						$reset = true;
-						
+
 						// Get the highest install version that is available from the package
 						// Get the highest install version that is available from the package
 						foreach ($installs as $install)
 						foreach ($installs as $install)
 						{
 						{
@@ -1683,12 +1694,12 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 							break;
 							break;
 						}
 						}
 					}
 					}
-					
+
 					// no uninstall found for this version, lets see if one exists for another
 					// 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']))
 					if ($packageInfo['can_uninstall'] === false && $uninstall->exists('@for') && empty($_SESSION['version_emulate']))
 					{
 					{
 						$reset = true;
 						$reset = true;
-						
+
 						// Get the highest install version that is available from the package
 						// Get the highest install version that is available from the package
 						foreach ($uninstalls as $uninstall)
 						foreach ($uninstalls as $uninstall)
 						{
 						{
@@ -1701,24 +1712,40 @@ function list_getPackages($start, $items_per_page, $sort, $params, $installed)
 				// Modification.
 				// Modification.
 				if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod')
 				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.
 				// Avatar package.
 				elseif ($packageInfo['type'] == 'avatar')
 				elseif ($packageInfo['type'] == 'avatar')
 				{
 				{
+					$sort_id[$packageInfo['type']]++;
 					$packages['avatar'][strtolower($packageInfo[$sort])] = md5($package);
 					$packages['avatar'][strtolower($packageInfo[$sort])] = md5($package);
 					$context['available_avatar'][md5($package)] = $packageInfo;
 					$context['available_avatar'][md5($package)] = $packageInfo;
 				}
 				}
 				// Language package.
 				// Language package.
 				elseif ($packageInfo['type'] == 'language')
 				elseif ($packageInfo['type'] == 'language')
 				{
 				{
+					$sort_id[$packageInfo['type']]++;
 					$packages['language'][strtolower($packageInfo[$sort])] = md5($package);
 					$packages['language'][strtolower($packageInfo[$sort])] = md5($package);
 					$context['available_language'][md5($package)] = $packageInfo;
 					$context['available_language'][md5($package)] = $packageInfo;
 				}
 				}
 				// Other stuff.
 				// Other stuff.
 				else
 				else
 				{
 				{
+					$sort_id['unknown']++;
 					$packages['unknown'][strtolower($packageInfo[$sort])] = md5($package);
 					$packages['unknown'][strtolower($packageInfo[$sort])] = md5($package);
 					$context['available_unknown'][md5($package)] = $packageInfo;
 					$context['available_unknown'][md5($package)] = $packageInfo;
 				}
 				}

+ 68 - 14
Sources/PersonalMessage.php

@@ -9,7 +9,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -35,7 +35,7 @@ function MessageMain()
 	// This file contains the basic functions for sending a PM.
 	// This file contains the basic functions for sending a PM.
 	require_once($sourcedir . '/Subs-Post.php');
 	require_once($sourcedir . '/Subs-Post.php');
 
 
-	loadLanguage('PersonalMessage');
+	loadLanguage('PersonalMessage+Drafts');
 
 
 	if (WIRELESS && WIRELESS_PROTOCOL == 'wap')
 	if (WIRELESS && WIRELESS_PROTOCOL == 'wap')
 		fatal_lang_error('wireless_error_notyet', false);
 		fatal_lang_error('wireless_error_notyet', false);
@@ -197,6 +197,7 @@ function MessageMain()
 		'send' => 'MessagePost',
 		'send' => 'MessagePost',
 		'send2' => 'MessagePost2',
 		'send2' => 'MessagePost2',
 		'settings' => 'MessageSettings',
 		'settings' => 'MessageSettings',
+		'showpmdrafts' => 'MessageDrafts',
 	);
 	);
 
 
 	if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
 	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
  * @param string $area
  */
  */
@@ -235,6 +236,12 @@ function messageIndexBar($area)
 					'label' => $txt['sent_items'],
 					'label' => $txt['sent_items'],
 					'custom_url' => $scripturl . '?action=pm;f=sent',
 					'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(
 		'labels' => array(
@@ -332,22 +339,25 @@ function messageIndexBar($area)
 	$menuOptions = array(
 	$menuOptions = array(
 		'current_area' => $area,
 		'current_area' => $area,
 		'disable_url_session_check' => true,
 		'disable_url_session_check' => true,
-		'toggle_url' => $current_page . ';togglebar',
-		'toggle_redirect_url' => $current_page,
 	);
 	);
 
 
 	// Actually create the menu!
 	// Actually create the menu!
 	$pm_include_data = createMenu($pm_areas, $menuOptions);
 	$pm_include_data = createMenu($pm_areas, $menuOptions);
 	unset($pm_areas);
 	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.
 	// Make a note of the Unique ID for this menu.
 	$context['pm_menu_id'] = $context['max_menu_id'];
 	$context['pm_menu_id'] = $context['max_menu_id'];
 	$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
 	$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
 
 
 	// Set the selected item.
 	// 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']))
 	if (!WIRELESS && !isset($_REQUEST['xml']))
 		$context['template_layers'][] = 'pm';
 		$context['template_layers'][] = 'pm';
 }
 }
@@ -843,7 +853,7 @@ function MessageFolder()
 		elseif (!empty($context['current_pm']))
 		elseif (!empty($context['current_pm']))
 			markMessages($display_pms, $context['current_label_id']);
 			markMessages($display_pms, $context['current_label_id']);
 	}
 	}
-	
+
 	// Build the conversation button array.
 	// Build the conversation button array.
 	if ($context['display_mode'] == 2)
 	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),
 			'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']) . '?\');"'),
 			'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
 		// Allow mods to add additional buttons here
 		call_integration_hook('integrate_conversation_buttons');
 		call_integration_hook('integrate_conversation_buttons');
 	}
 	}
@@ -1771,6 +1781,18 @@ function MessagePost()
 
 
 	$modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']);
 	$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.
 	// Needed for the WYSIWYG editor.
 	require_once($sourcedir . '/Subs-Editor.php');
 	require_once($sourcedir . '/Subs-Editor.php');
 
 
@@ -1806,6 +1828,28 @@ function MessagePost()
 	checkSubmitOnce('register');
 	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...
  * 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.
 	// Set each of the errors for the template.
 	loadLanguage('Errors');
 	loadLanguage('Errors');
-	
+
 	$context['error_type'] = 'minor';
 	$context['error_type'] = 'minor';
-	
+
 	$context['post_error'] = array(
 	$context['post_error'] = array(
 		'messages' => array(),
 		'messages' => array(),
 		// @todo error handling: maybe fatal errors can be error_type => serious
 		// @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']);
 				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
 			$context['post_error']['messages'][] = $txt['error_' . $error_type];
 			$context['post_error']['messages'][] = $txt['error_' . $error_type];
 		}
 		}
-		
+
 		// If it's not a minor error flag it as such.
 		// 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')))
 		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
 			$context['error_type'] = 'serious';
 			$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.
 	// We need to load the editor once more.
 	require_once($sourcedir . '/Subs-Editor.php');
 	require_once($sourcedir . '/Subs-Editor.php');
 
 
@@ -2162,9 +2210,7 @@ function MessagePost2()
 		$context['require_verification'] = create_control_verification($verificationOptions, true);
 		$context['require_verification'] = create_control_verification($verificationOptions, true);
 
 
 		if (is_array($context['require_verification']))
 		if (is_array($context['require_verification']))
-		{
 			$post_errors = array_merge($post_errors, $context['require_verification']);
 			$post_errors = array_merge($post_errors, $context['require_verification']);
-		}
 	}
 	}
 
 
 	// If they did, give a chance to make ammends.
 	// If they did, give a chance to make ammends.
@@ -2207,6 +2253,14 @@ function MessagePost2()
 		return messagePostError(array(), $namedRecipientList, $recipientList);
 		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.
 	// 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')))
 	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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -213,7 +213,7 @@ function Vote()
 		$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
 		$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);
 		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?
 	// Maybe let a social networking mod log this, or something?
 	call_integration_hook('integrate_poll_vote', array(&$row['id_poll'], &$pollOptions));
 	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));
 	call_integration_hook('integrate_poll_add_edit', array($bcinfo['id_poll'], $isEdit));
 
 
 	// Off we go.
 	// Off we go.
@@ -976,7 +976,7 @@ function RemovePoll()
 			'no_poll' => 0,
 			'no_poll' => 0,
 		)
 		)
 	);
 	);
-	
+
 	// A mod might have logged this (social network?), so let them remove, it too
 	// A mod might have logged this (social network?), so let them remove, it too
 	call_integration_hook('integrate_poll_remove', array(&$pollID));
 	call_integration_hook('integrate_poll_remove', array(&$pollID));
 
 

+ 91 - 252
Sources/Post.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -18,7 +18,8 @@ if (!defined('SMF'))
 	die('Hacking attempt...');
 	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.
  * - additionally handles previews of posts.
  * - @uses the Post template and language file, main sub template.
  * - @uses the Post template and language file, main sub template.
  * - allows wireless access using the protocol_post 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');
 	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']))
 	if (isset($_REQUEST['xml']))
 	{
 	{
 		$context['sub_template'] = 'post';
 		$context['sub_template'] = 'post';
@@ -464,7 +453,7 @@ function Post($post_errors = array())
 		}
 		}
 
 
 		// Only show the preview stuff if they hit Preview.
 		// 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...
 			// Set up the preview message and subject and censor them...
 			$context['preview_message'] = $form_message;
 			$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,
 					m.poster_name, m.poster_email, m.subject, m.icon, m.approved,
 					IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach,
 					IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach,
 					a.approved AS attachment_approved, t.id_member_started AS id_member_poster,
 					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
 				FROM {db_prefix}messages AS m
 					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
 					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}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}
 				WHERE m.id_msg = {int:id_msg}
 					AND m.id_topic = {int:current_topic}',
 					AND m.id_topic = {int:current_topic}',
 				array(
 				array(
 					'current_topic' => $topic,
 					'current_topic' => $topic,
 					'attachment_type' => 0,
 					'attachment_type' => 0,
 					'id_msg' => $_REQUEST['msg'],
 					'id_msg' => $_REQUEST['msg'],
+					'announce_action' => 'announce_topic',
 				)
 				)
 			);
 			);
 			// The message they were trying to edit was most likely deleted.
 			// 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);
 				$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....
 			// Allow moderators to change names....
 			if (allowedTo('moderate_forum') && !empty($topic))
 			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,
 				m.poster_name, m.poster_email, m.subject, m.icon, m.approved,
 				IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach,
 				IFNULL(a.size, -1) AS filesize, a.filename, a.id_attach,
 				a.approved AS attachment_approved, t.id_member_started AS id_member_poster,
 				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
 			FROM {db_prefix}messages AS m
 				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
 				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}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}
 			WHERE m.id_msg = {int:id_msg}
 				AND m.id_topic = {int:current_topic}',
 				AND m.id_topic = {int:current_topic}',
 			array(
 			array(
 				'current_topic' => $topic,
 				'current_topic' => $topic,
 				'attachment_type' => 0,
 				'attachment_type' => 0,
 				'id_msg' => $_REQUEST['msg'],
 				'id_msg' => $_REQUEST['msg'],
+				'announce_action' => 'announce_topic',
 			)
 			)
 		);
 		);
 		// The message they were trying to edit was most likely deleted.
 		// The message they were trying to edit was most likely deleted.
-		//@todo Change this error message?
 		if ($smcFunc['db_num_rows']($request) == 0)
 		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);
 		$row = $smcFunc['db_fetch_assoc']($request);
 
 
 		$attachment_stuff = array($row);
 		$attachment_stuff = array($row);
@@ -653,6 +651,12 @@ function Post($post_errors = array())
 		else
 		else
 			isAllowedTo('modify_any');
 			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?
 		// When was it last modified?
 		if (!empty($row['modified_time']))
 		if (!empty($row['modified_time']))
 			$context['last_modified'] = timeformat($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.
 		// If there are attachments, calculate the total size and how many.
 		$context['attachments']['total_size'] = 0;
 		$context['attachments']['total_size'] = 0;
 		$context['attachments']['quantity'] = 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 this isn't a new post, check the current attachments.
 		if (isset($_REQUEST['msg']))
 		if (isset($_REQUEST['msg']))
@@ -913,12 +906,12 @@ function Post($post_errors = array())
 				// Show any errors which might of occured.
 				// Show any errors which might of occured.
 				if (!empty($attachment['errors']))
 				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)
 					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.
 					// Take out the trash.
 					unset($_SESSION['temp_attachments'][$attachID]);
 					unset($_SESSION['temp_attachments'][$attachID]);
@@ -1035,6 +1028,17 @@ function Post($post_errors = array())
 	$context['subject'] = addcslashes($form_subject, '"');
 	$context['subject'] = addcslashes($form_subject, '"');
 	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
 	$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.
 	// Needed for the editor and message icons.
 	require_once($sourcedir . '/Subs-Editor.php');
 	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.
  * requires various permissions depending on the action.
  * handles attachment, post, and calendar saving.
  * handles attachment, post, and calendar saving.
  * sends off notifications, and allows for announcements and moderation.
  * sends off notifications, and allows for announcements and moderation.
@@ -1168,22 +1173,6 @@ function Post2()
 	// No need!
 	// No need!
 	$context['robot_no_index'] = true;
 	$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.
 	// Previewing? Go back to start.
 	if (isset($_REQUEST['preview']))
 	if (isset($_REQUEST['preview']))
 		return Post();
 		return Post();
@@ -1213,6 +1202,10 @@ function Post2()
 	require_once($sourcedir . '/Subs-Post.php');
 	require_once($sourcedir . '/Subs-Post.php');
 	loadLanguage('Post');
 	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.
 	// First check to see if they are trying to delete any current attachments.
 	if (isset($_POST['attach_del']))
 	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')));
 	$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']))
 	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 this isn't a new topic load the topic info that we need.
 	if (!empty($topic))
 	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')))
 		if (isset($_POST['sticky']) && (empty($modSettings['enableStickyTopics']) || $_POST['sticky'] == $topic_info['is_sticky'] || !allowedTo('make_sticky')))
 			unset($_POST['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 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'])
 		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')))
 		if (isset($_POST['sticky']) && (empty($modSettings['enableStickyTopics']) || empty($_POST['sticky']) || !allowedTo('make_sticky')))
 			unset($_POST['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'];
 		$posterIsGuest = $user_info['is_guest'];
 	}
 	}
 	// Modifying an existing message?
 	// Modifying an existing message?
@@ -1626,6 +1453,13 @@ function Post2()
 				$moderationAction = true;
 				$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']);
 		$posterIsGuest = empty($row['id_member']);
 
 
 		// Can they approve it?
 		// Can they approve it?
@@ -1856,6 +1690,7 @@ function Post2()
 				'tmp_name' => $attachment['tmp_name'],
 				'tmp_name' => $attachment['tmp_name'],
 				'size' => isset($attachment['size']) ? $attachment['size'] : 0,
 				'size' => isset($attachment['size']) ? $attachment['size'] : 0,
 				'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
 				'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
+				'id_folder' => $attachment['id_folder'],
 				'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
 				'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
 				'errors' => $attachment['errors'],
 				'errors' => $attachment['errors'],
 			);
 			);
@@ -1869,17 +1704,14 @@ function Post2()
 						$attachIDs[] = $attachmentOptions['thumb'];
 						$attachIDs[] = $attachmentOptions['thumb'];
 				}
 				}
 			}
 			}
+			else
+				$attach_errors[] = '<dt>&nbsp;</dt>';
 
 
 			if (!empty($attachmentOptions['errors']))
 			if (!empty($attachmentOptions['errors']))
 			{
 			{
-				if (isset($br))
-					$attach_errors[] = '<dt>&nbsp;</dt>';
-				else
-					$br = '';
-
 				// Sort out the errors for display and delete any associated files.
 				// Sort out the errors for display and delete any associated files.
 				$attach_errors[] = '<dt>' . vsprintf($txt['attach_warning'], $attachment['name']) . '</dt>';
 				$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)
 				foreach ($attachmentOptions['errors'] as $error)
 				{
 				{
 					if (!is_array($error))
 					if (!is_array($error))
@@ -1931,7 +1763,7 @@ function Post2()
 			$pollOptions,
 			$pollOptions,
 			array('id_poll', 'id_choice')
 			array('id_poll', 'id_choice')
 		);
 		);
-		
+
 		call_integration_hook('integrate_poll_add_edit', array($id_poll, false));
 		call_integration_hook('integrate_poll_add_edit', array($id_poll, false));
 	}
 	}
 	else
 	else
@@ -2185,6 +2017,7 @@ function Post2()
 		$context['error_message'] = '<dl>';
 		$context['error_message'] = '<dl>';
 		$context['error_message'] .= implode("\n", $attach_errors);
 		$context['error_message'] .= implode("\n", $attach_errors);
 		$context['error_message'] .= '</dl>';
 		$context['error_message'] .= '</dl>';
+		$context['error_title'] = $txt['attach_error_title'];
 
 
 		$context['linktree'][] = array(
 		$context['linktree'][] = array(
 			'url' => $scripturl . '?topic=' . $topic . '.0',
 			'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.
  * checks the topic announcement permissions and loads the announcement template.
  * requires the announce_topic permission.
  * requires the announce_topic permission.
  * uses the ManageMembers template and Post language file.
  * 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.
  * Allow a user to chose the membergroups to send the announcement to.
+ *
  * lets the user select the membergroups that will receive the topic announcement.
  * lets the user select the membergroups that will receive the topic announcement.
  */
  */
 function AnnouncementSelectMembergroup()
 function AnnouncementSelectMembergroup()
@@ -2323,6 +2158,7 @@ function AnnouncementSelectMembergroup()
 
 
 /**
 /**
  * Send the announcement in chunks.
  * Send the announcement in chunks.
+ *
  * splits the members to be sent a topic announcement into chunks.
  * splits the members to be sent a topic announcement into chunks.
  * composes notification messages in all languages needed.
  * composes notification messages in all languages needed.
  * does the actual sending of the topic announcements in chunks.
  * does the actual sending of the topic announcements in chunks.
@@ -2335,9 +2171,6 @@ function AnnouncementSend()
 
 
 	checkSession();
 	checkSession();
 
 
-	// @todo Might need an interface?
-	$chunkSize = empty($modSettings['mail_queue']) ? 50 : 500;
-
 	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
 	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
 	$groups = array_merge($board_info['groups'], array(1));
 	$groups = array_merge($board_info['groups'], array(1));
 
 
@@ -2377,26 +2210,27 @@ function AnnouncementSend()
 	$request = $smcFunc['db_query']('', '
 	$request = $smcFunc['db_query']('', '
 		SELECT mem.id_member, mem.email_address, mem.lngfile
 		SELECT mem.id_member, mem.email_address, mem.lngfile
 		FROM {db_prefix}members AS mem
 		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.notify_announcements = {int:notify_announcements}' : '') . '
 			AND mem.is_activated = {int:is_activated}
 			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}
 			AND mem.id_member > {int:start}
 		ORDER BY mem.id_member
 		ORDER BY mem.id_member
-		LIMIT ' . $chunkSize,
+		LIMIT {int:chunk_size}',
 		array(
 		array(
-			'current_member' => $user_info['id'],
 			'group_list' => $_POST['who'],
 			'group_list' => $_POST['who'],
 			'notify_announcements' => 1,
 			'notify_announcements' => 1,
 			'is_activated' => 1,
 			'is_activated' => 1,
 			'start' => $context['start'],
 			'start' => $context['start'],
 			'additional_group_list' => implode(', mem.additional_groups) != 0 OR FIND_IN_SET(', $_POST['who']),
 			'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.
 	// All members have received a mail. Go to the next screen.
 	if ($smcFunc['db_num_rows']($request) == 0)
 	if ($smcFunc['db_num_rows']($request) == 0)
 	{
 	{
+		logAction('announce_topic', array('topic' => $topic), 'user');
 		if (!empty($_REQUEST['move']) && allowedTo('move_any'))
 		if (!empty($_REQUEST['move']) && allowedTo('move_any'))
 			redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback'));
 			redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback'));
 		elseif (!empty($_REQUEST['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.
  * 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.)
  * 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.
  * 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.
  * Get the topic for display purposes.
+ *
  * gets a summary of the most recent posts in a topic.
  * gets a summary of the most recent posts in a topic.
  * depends on the topicSummaryPosts setting.
  * depends on the topicSummaryPosts setting.
  * if you are editing a post, only shows posts previous to that post.
  * 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 the Post language file.
  * uses special (sadly browser dependent) javascript to parse entities for internationalization reasons.
  * uses special (sadly browser dependent) javascript to parse entities for internationalization reasons.
  * accessed with ?action=quotefast.
  * 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()
 function JavaScriptModify()
 {
 {
 	global $sourcedir, $modSettings, $board, $topic, $txt;
 	global $sourcedir, $modSettings, $board, $topic, $txt;

+ 4 - 4
Sources/PostModeration.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -522,7 +522,7 @@ function UnapprovedAttachments()
 /**
 /**
  * Callback function for UnapprovedAttachments
  * Callback function for UnapprovedAttachments
  * retrieve all the attachments waiting for approval the approver can approve
  * retrieve all the attachments waiting for approval the approver can approve
- * 
+ *
  * @param int $start
  * @param int $start
  * @param int $items_per_page
  * @param int $items_per_page
  * @param string $sort
  * @param string $sort
@@ -602,7 +602,7 @@ function list_getUnapprovedAttachments($start, $items_per_page, $sort, $approve_
 /**
 /**
  * Callback function for UnapprovedAttachments
  * Callback function for UnapprovedAttachments
  * count all the attachments waiting for approval the approver can approve
  * count all the attachments waiting for approval the approver can approve
- * 
+ *
  * @param int $start
  * @param int $start
  * @param int $items_per_page
  * @param int $items_per_page
  * @param string $sort
  * @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']));
 				(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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Profile-Actions.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 23 - 4
Sources/Profile-Modify.php

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

+ 28 - 22
Sources/Profile-View.php

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

+ 14 - 4
Sources/Profile.php

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

+ 3 - 3
Sources/QueryString.php

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

+ 7 - 7
Sources/Recent.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -432,7 +432,7 @@ function UnreadTopics()
 		header('HTTP/1.1 403 Forbidden');
 		header('HTTP/1.1 403 Forbidden');
 		die;
 		die;
 	}
 	}
-	
+
 	$context['showCheckboxes'] = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read'];
 	$context['showCheckboxes'] = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $settings['show_mark_read'];
 
 
 	$context['showing_all_topics'] = isset($_GET['all']);
 	$context['showing_all_topics'] = isset($_GET['all']);
@@ -1233,7 +1233,7 @@ function UnreadTopics()
 					'name' => $row['first_poster_name'],
 					'name' => $row['first_poster_name'],
 					'id' => $row['id_first_member'],
 					'id' => $row['id_first_member'],
 					'href' => $scripturl . '?action=profile;u=' . $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']),
 				'time' => timeformat($row['first_poster_time']),
 				'timestamp' => forum_time(true, $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['querystring_board_limits'] = sprintf($context['querystring_board_limits'], $_REQUEST['start']);
 	$context['topics_to_mark'] = implode('-', $topic_ids);
 	$context['topics_to_mark'] = implode('-', $topic_ids);
-	
+
 	if ($settings['show_mark_read'])
 	if ($settings['show_mark_read'])
 	{
 	{
 		// Build the recent button array.
 		// Build the recent button array.
@@ -1335,9 +1335,9 @@ function UnreadTopics()
 					'lang' => true,
 					'lang' => true,
 					'url' => 'javascript:document.quickModForm.submit();',
 					'url' => 'javascript:document.quickModForm.submit();',
 				);
 				);
-				
+
 			if (!empty($context['topics']) && !$context['showing_all_topics'])
 			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']))
 		elseif (!$is_topics && isset($context['topics_to_mark']))
 		{
 		{
@@ -1357,7 +1357,7 @@ function UnreadTopics()
 		// Allow mods to add additional buttons here
 		// Allow mods to add additional buttons here
 		call_integration_hook('integrate_recent_buttons');
 		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!)
 	// 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');
  	call_integration_hook('integrate_unread_list');
 }
 }

+ 11 - 19
Sources/Register.php

@@ -9,7 +9,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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');
 			$context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.txt'), true, 'agreement');
 		else
 		else
 			$context['agreement'] = '';
 			$context['agreement'] = '';
-		
+
 		// Nothing to show, lets disable registration and inform the admin of this error
 		// Nothing to show, lets disable registration and inform the admin of this error
 		if (empty($context['agreement']))
 		if (empty($context['agreement']))
 		{
 		{
@@ -522,7 +522,11 @@ function Register2($verifiedOpenID = false)
  */
  */
 function Activate()
 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');
 	loadLanguage('Login');
 	loadTemplate('Login');
 	loadTemplate('Login');
@@ -832,26 +836,14 @@ function RegisterCheckUsername()
 	// This is XML!
 	// This is XML!
 	loadTemplate('Xml');
 	loadTemplate('Xml');
 	$context['sub_template'] = 'check_username';
 	$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;
 	$context['valid_username'] = true;
 
 
 	// Clean it up like mother would.
 	// 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']);
 	$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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -70,7 +70,7 @@ function RemindPick()
 	// You must enter a username/email address.
 	// You must enter a username/email address.
 	if (empty($where))
 	if (empty($where))
 		fatal_lang_error('username_no_exist', false);
 		fatal_lang_error('username_no_exist', false);
-		
+
 	// Make sure we are not being slammed
 	// Make sure we are not being slammed
 	spamProtection('remind');
 	spamProtection('remind');
 
 

+ 44 - 24
Sources/RemoveTopic.php

@@ -1,11 +1,14 @@
 <?php
 <?php
 
 
 /**
 /**
+ * The contents of this file handle the deletion of topics, posts, and related
+ * paraphernalia.
+ * 
  * Simple Machines Forum (SMF)
  * Simple Machines Forum (SMF)
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.0
  * @version 2.0
@@ -17,23 +20,12 @@ if (!defined('SMF'))
 /*	The contents of this file handle the deletion of topics, posts, and related
 /*	The contents of this file handle the deletion of topics, posts, and related
 	paraphernalia.  It has the following functions:
 	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()
 function RemoveTopic2()
 {
 {
 	global $user_info, $topic, $board, $sourcedir, $smcFunc, $context, $modSettings;
 	global $user_info, $topic, $board, $sourcedir, $smcFunc, $context, $modSettings;
@@ -84,7 +76,10 @@ function RemoveTopic2()
 	redirectexit('board=' . $board . '.0');
 	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()
 function DeleteMessage()
 {
 {
 	global $user_info, $topic, $board, $modSettings, $smcFunc;
 	global $user_info, $topic, $board, $modSettings, $smcFunc;
@@ -151,7 +146,10 @@ function DeleteMessage()
 		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
 		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()
 function RemoveOldTopics2()
 {
 {
 	global $modSettings, $smcFunc;
 	global $modSettings, $smcFunc;
@@ -221,7 +219,13 @@ function RemoveOldTopics2()
 	redirectexit('action=admin;area=maintain;sa=topics;done=purgeold');
 	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)
 function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false)
 {
 {
 	global $sourcedir, $modSettings, $smcFunc;
 	global $sourcedir, $modSettings, $smcFunc;
@@ -232,7 +236,7 @@ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = fal
 	// Only a single topic.
 	// Only a single topic.
 	if (is_numeric($topics))
 	if (is_numeric($topics))
 		$topics = array($topics);
 		$topics = array($topics);
-		
+
 	// Decrease the post counts.
 	// Decrease the post counts.
 	if ($decreasePostCount)
 	if ($decreasePostCount)
 	{
 	{
@@ -533,7 +537,7 @@ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = fal
 			'topics' => $topics,
 			'topics' => $topics,
 		)
 		)
 	);
 	);
-	
+
 	// Maybe there's a mod that wants to delete topic related data of its own
 	// Maybe there's a mod that wants to delete topic related data of its own
  	call_integration_hook('integrate_remove_topics', array($topics));
  	call_integration_hook('integrate_remove_topics', array($topics));
 
 
@@ -551,7 +555,15 @@ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = fal
 	updateLastMessages($updates);
 	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)
 function removeMessage($message, $decreasePostCount = true)
 {
 {
 	global $board, $sourcedir, $modSettings, $user_info, $smcFunc, $context;
 	global $board, $sourcedir, $modSettings, $user_info, $smcFunc, $context;
@@ -962,7 +974,7 @@ function removeMessage($message, $decreasePostCount = true)
 			'id_msg' => $message,
 			'id_msg' => $message,
 		);
 		);
 		removeAttachments($attachmentQuery);
 		removeAttachments($attachmentQuery);
-		
+
 		// Allow mods to remove message related data of their own (likes, maybe?)
 		// Allow mods to remove message related data of their own (likes, maybe?)
 		call_integration_hook('integrate_remove_message', array($message));
 		call_integration_hook('integrate_remove_message', array($message));
 	}
 	}
@@ -984,6 +996,9 @@ function removeMessage($message, $decreasePostCount = true)
 	return false;
 	return false;
 }
 }
 
 
+/**
+ * Move back a topic from the recycle board to its original board.
+ */
 function RestoreTopic()
 function RestoreTopic()
 {
 {
 	global $context, $smcFunc, $modSettings, $sourcedir;
 	global $context, $smcFunc, $modSettings, $sourcedir;
@@ -1215,7 +1230,9 @@ function RestoreTopic()
 	redirectexit();
 	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)
 function mergePosts($msgs = array(), $from_topic, $target_topic)
 {
 {
 	global $context, $smcFunc, $modSettings, $sourcedir;
 	global $context, $smcFunc, $modSettings, $sourcedir;
@@ -1468,6 +1485,9 @@ function mergePosts($msgs = array(), $from_topic, $target_topic)
 	updateLastMessages(array($from_board, $target_board));
 	updateLastMessages(array($from_board, $target_board));
 }
 }
 
 
+/**
+ * Try to determine if the topic has already been deleted by another user.
+ */
 function removeDeleteConcurrence()
 function removeDeleteConcurrence()
 {
 {
 	global $modSettings, $board, $topic, $smcFunc, $scripturl, $context;
 	global $modSettings, $board, $topic, $smcFunc, $scripturl, $context;

+ 1 - 1
Sources/RepairBoards.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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"
  * 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 fill context with relevant data. Secondly, the choice of sub-template
  * will determine how this data is shown to the user
  * will determine how this data is shown to the user
- * 
+ *
  * Functions ending with "Report" are responsible for generating data for reporting.
  * Functions ending with "Report" are responsible for generating data for reporting.
  * They are all called from ReportsMain.
  * They are all called from ReportsMain.
  * Never access the context directly, but use the data handling functions to do so.
  * Never access the context directly, but use the data handling functions to do so.
- * 
+ *
  * Simple Machines Forum (SMF)
  * Simple Machines Forum (SMF)
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -103,16 +103,16 @@ function ReportsMain()
 
 
 	// Make the page title more descriptive.
 	// 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']);
 	$context['page_title'] .= ' - ' . (isset($txt['gr_type_' . $context['report_type']]) ? $txt['gr_type_' . $context['report_type']] : $context['report_type']);
-	
+
 	// Build the reports button array.
 	// Build the reports button array.
 	$context['report_buttons'] = array(
 	$context['report_buttons'] = array(
 		'generate_reports' => array('text' => 'generate_reports', 'image' => 'print.png', 'lang' => true, 'url' => $scripturl . '?action=admin;area=reports', 'active' => true),
 		'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"'),
 		'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
 	// Allow mods to add additional buttons here
-	call_integration_hook('integrate_report_buttons');	
-	
+	call_integration_hook('integrate_report_buttons');
+
 	// Now generate the data.
 	// Now generate the data.
 	$context['report_types'][$context['report_type']]['function']();
 	$context['report_types'][$context['report_type']]['function']();
 
 
@@ -785,7 +785,7 @@ function StaffReport()
  * context, ready for filling using addData().
  * context, ready for filling using addData().
  * Fills the context variable current_table with the ID of the table created.
  * 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.
  * 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 $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 $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?
  * @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
  * if any key in the incoming data begins with '#sep#', the function
  * will add a separator accross the table at this point.
  * will add a separator accross the table at this point.
  * once the incoming data has been sanitized, it is added to the table.
  * once the incoming data has been sanitized, it is added to the table.
- * 
+ *
  * @param array $inc_data
  * @param array $inc_data
  * @param int $custom_table = null
  * @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.
  * Add a separator row, only really used when adding data by rows.
- * 
+ *
  * @param string $title = ''
  * @param string $title = ''
  * @param string $custom_table = null
  * @param string $custom_table = null
- * 
+ *
  * @return bool returns false if there are no tables
  * @return bool returns false if there are no tables
  */
  */
 function addSeparator($title = '', $custom_table = null)
 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.
  * 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
  * 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.
  * 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
  * method specifies whether the data passed to addData represents a new
@@ -975,7 +975,7 @@ function finishTables()
  * addData().
  * addData().
  * if reverse is set to true, then the values of the variable "keys"
  * if reverse is set to true, then the values of the variable "keys"
  * are used as oppossed to the keys(!
  * are used as oppossed to the keys(!
- * 
+ *
  * @param string $method = 'rows' rows or cols
  * @param string $method = 'rows' rows or cols
  * @param array $keys = array()
  * @param array $keys = array()
  * @param bool $reverse = false
  * @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.
  * This file is automatically called and handles all manner of scheduled things.
- * 
+ *
  * Simple Machines Forum (SMF)
  * Simple Machines Forum (SMF)
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -28,6 +28,8 @@ function AutoTask()
 		ReduceMailQueue();
 		ReduceMailQueue();
 	else
 	else
 	{
 	{
+		call_integration_hook('integrate_autotask_include');
+
 		// Select the next task to do.
 		// Select the next task to do.
 		$request = $smcFunc['db_query']('', '
 		$request = $smcFunc['db_query']('', '
 			SELECT id_task, task, next_time, time_offset, time_regularity, time_unit
 			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
  * 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;
 	global $smcFunc, $sourcedir;
-	
+
 	// init
 	// init
 	$topics = array();
 	$topics = array();
-	
+
 	// We will need this for lanaguage files
 	// We will need this for lanaguage files
 	loadEssentialThemeData();
 	loadEssentialThemeData();
-	
+
 	// Find all of the old MOVE topic notices that were set to expire
 	// Find all of the old MOVE topic notices that were set to expire
 	$request = $smcFunc['db_query']('', '
 	$request = $smcFunc['db_query']('', '
 		SELECT id_topic
 		SELECT id_topic
@@ -1705,11 +1707,11 @@ function scheduled_remove_topic_redirect()
 			'redirect_expires' => time(),
 			'redirect_expires' => time(),
 		)
 		)
 	);
 	);
-	
+
 	while ($row = $smcFunc['db_fetch_row']($request))
 	while ($row = $smcFunc['db_fetch_row']($request))
 		$topics[] = $row[0];
 		$topics[] = $row[0];
 	$smcFunc['db_free_result']($request);
 	$smcFunc['db_free_result']($request);
-	
+
 	// Zap, your gone
 	// Zap, your gone
 	if (count($topics) > 0)
 	if (count($topics) > 0)
 	{
 	{
@@ -1719,3 +1721,43 @@ function scheduled_remove_topic_redirect()
 
 
 	return true;
 	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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -267,15 +267,32 @@ function PlushSearch2()
 	}
 	}
 
 
 	$weight_factors = array(
 	$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 = array();
 	$weight_total = 0;
 	$weight_total = 0;
@@ -574,7 +591,7 @@ function PlushSearch2()
 		'num_replies',
 		'num_replies',
 		'id_msg',
 		'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']))
 	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
 		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
 		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';
 	$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;
 	$recentMsg = $modSettings['maxMsgID'] - $minMsg;
 
 
 	// *** Parse the search query
 	// *** 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.
 	 * 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?
 	 * @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');
 	$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?
 	// What are we searching for?
 	if (empty($search_params['search']))
 	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), '\\\'') . '[[:>:]]';
 							$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 * (';
 					$relevance = '1000 * (';
 					foreach ($weight_factors as $type => $value)
 					foreach ($weight_factors as $type => $value)
 					{
 					{
 						$relevance .= $weight[$type];
 						$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';
 					$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['select']['num_matches'] = '1 AS num_matches';
 
 
 					$main_query['weights'] = array(
 					$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']))
 					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), '\\\'') . '[[:>:]]';
 								$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?
 						// Nothing to search for?
 						if (empty($subject_query['where']))
 						if (empty($subject_query['where']))
@@ -1371,7 +1391,7 @@ function PlushSearch2()
 
 
 					if ($numSubjectResults !== 0)
 					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)';
 						$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)
 						if (!$createTemporary)
 							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
 							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
@@ -1534,7 +1554,7 @@ function PlushSearch2()
 						$main_query['parameters']['board_query'] = $boardQuery;
 						$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?
 				// Did we either get some indexed results, or otherwise did not do an indexed query?
 				if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params))
 				if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params))
@@ -1544,8 +1564,8 @@ function PlushSearch2()
 					foreach ($main_query['weights'] as $type => $value)
 					foreach ($main_query['weights'] as $type => $value)
 					{
 					{
 						$relevance .= $weight[$type];
 						$relevance .= $weight[$type];
-						if (!empty($value))
-							$relevance .= ' * ' . $value;
+						if (!empty($value['search']))
+							$relevance .= ' * ' . $value['search'];
 						$relevance .= ' + ';
 						$relevance .= ' + ';
 						$new_weight_total += $weight[$type];
 						$new_weight_total += $weight[$type];
 					}
 					}
@@ -1608,14 +1628,14 @@ function PlushSearch2()
 				if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0)
 				if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0)
 				{
 				{
 					$relevance = '1000 * (';
 					$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';
 					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
 
 
 					$usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts));
 					$usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts));
@@ -1625,12 +1645,12 @@ function PlushSearch2()
 						SELECT
 						SELECT
 							{int:id_search},
 							{int:id_search},
 							t.id_topic,
 							t.id_topic,
-							' . $relevance. ',
+							' . $relevance . ',
 							t.id_first_msg,
 							t.id_first_msg,
 							1
 							1
 						FROM {db_prefix}topics AS t
 						FROM {db_prefix}topics AS t
 							INNER JOIN {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (lst.id_topic = t.id_topic)'
 							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']) ? '' : '
 						. (empty($modSettings['search_max_results']) ? '' : '
 						LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])),
 						LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])),
 						array(
 						array(
@@ -2056,18 +2076,6 @@ function prepareSearchContext($reset = false)
 		$context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']);
 		$context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']);
 		$context['can_markread'] = $context['user']['is_logged'];
 		$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');
 		$context['qmod_actions'] = array('remove', 'lock', 'sticky', 'move', 'merge', 'restore', 'markread');
 		call_integration_hook('integrate_quick_mod_actions_search');
 		call_integration_hook('integrate_quick_mod_actions_search');
 	}
 	}
@@ -2106,7 +2114,7 @@ function prepareSearchContext($reset = false)
 	);
 	);
 	$counter++;
 	$counter++;
 
 
-	call_integration_hook('integrate_search_message_context', array($counter, $output));
+	call_integration_hook('integrate_search_message_context', array($counter, &$output));
 
 
 	return $output;
 	return $output;
 }
 }

+ 6 - 6
Sources/SearchAPI-Custom.php

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

+ 54 - 59
Sources/SearchAPI-Fulltext.php

@@ -5,7 +5,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -22,9 +22,6 @@ class fulltext_search
 	public $min_smf_version = 'SMF 2.1 Alpha 1';
 	public $min_smf_version = 'SMF 2.1 Alpha 1';
 	// Is it supported?
 	// Is it supported?
 	public $is_supported = true;
 	public $is_supported = true;
-
-	// Can we do a boolean search - tested on construct.
-	protected $canDoBooleanSearch = false;
 	// What words are banned?
 	// What words are banned?
 	protected $bannedWords = array();
 	protected $bannedWords = array();
 	// What is the minimum word length?
 	// What is the minimum word length?
@@ -32,6 +29,10 @@ class fulltext_search
 	// What databases support the fulltext index?
 	// What databases support the fulltext index?
 	protected $supported_databases = array('mysql');
 	protected $supported_databases = array('mysql');
 
 
+	/**
+	 * fulltext_search::__construct()
+	 *
+	 */
 	public function __construct()
 	public function __construct()
 	{
 	{
 		global $smcFunc, $db_connection, $modSettings, $db_type;
 		global $smcFunc, $db_connection, $modSettings, $db_type;
@@ -43,14 +44,19 @@ class fulltext_search
 			return;
 			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->bannedWords = empty($modSettings['search_banned_words']) ? array() : explode(',', $modSettings['search_banned_words']);
 		$this->min_word_length = $this->_getMinWordLength();
 		$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)
 	public function supportsMethod($methodName, $query_params = null)
 	{
 	{
 		switch ($methodName)
 		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()
 	protected function _getMinWordLength()
 	{
 	{
 		global $smcFunc;
 		global $smcFunc;
@@ -92,7 +104,7 @@ class fulltext_search
 
 
 		return $min_word_length;
 		return $min_word_length;
 	}
 	}
-
+	
 	/**
 	/**
 	 * callback function for usort used to sort the fulltext results.
 	 * callback function for usort used to sort the fulltext results.
 	 * the order of sorting is: large words, small words, large words that
 	 * the order of sorting is: large words, small words, large words that
@@ -103,26 +115,34 @@ class fulltext_search
 	 */
 	 */
 	public function searchSort($a, $b)
 	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);
 		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)
 	public function prepareIndexes($word, &$wordsSearch, &$wordsExclude, $isExcluded)
 	{
 	{
 		global $modSettings, $smcFunc;
 		global $modSettings, $smcFunc;
 
 
 		$subwords = text2words($word, null, false);
 		$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
 			// this is harder on the server so we are restrictive here
 			if (count($subwords) > 1 && preg_match('~[.:@$]~', $word))
 			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)
 	public function indexedWordQuery($words, $search_data)
 	{
 	{
 		global $modSettings, $smcFunc;
 		global $modSettings, $smcFunc;
@@ -225,7 +229,7 @@ class fulltext_search
 			$query_where[] = 'MATCH (body) AGAINST ({string:body_match})';
 			$query_where[] = 'MATCH (body) AGAINST ({string:body_match})';
 			$query_params['body_match'] = implode(' ', array_diff($words['indexed_words'], $query_params['excluded_index_words']));
 			$query_params['body_match'] = implode(' ', array_diff($words['indexed_words'], $query_params['excluded_index_words']));
 		}
 		}
-		elseif ($this->canDoBooleanSearch)
+		else
 		{
 		{
 			$query_params['boolean_match'] = '';
 			$query_params['boolean_match'] = '';
 
 
@@ -240,15 +244,6 @@ class fulltext_search
 			if ($query_params['boolean_match'])
 			if ($query_params['boolean_match'])
 				$query_where[] = 'MATCH (body) AGAINST ({string:boolean_match} IN BOOLEAN MODE)';
 				$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'] ? ( '
 		$ignoreRequest = $smcFunc['db_search_query']('insert_into_log_messages_fulltext', ($smcFunc['db_support_ignore'] ? ( '
 			INSERT IGNORE INTO {db_prefix}' . $search_data['insert_into'] . '
 			INSERT IGNORE INTO {db_prefix}' . $search_data['insert_into'] . '

+ 1 - 1
Sources/SearchAPI-Standard.php

@@ -5,7 +5,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 25 - 14
Sources/Security.php

@@ -8,7 +8,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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.
  * 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.
  * 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.
  * 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
  * @param string $type = admin
  */
  */
 function validateSession($type = 'admin')
 function validateSession($type = 'admin')
@@ -97,6 +98,7 @@ function validateSession($type = 'admin')
  * Require a user who is logged in. (not a guest.)
  * 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.
  * 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.
  * Message is what to tell them when asking them to login.
+ *
  * @param string $message = ''
  * @param string $message = ''
  */
  */
 function is_not_guest($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.
  * Checks if the user is banned, and if so dies with an error.
  * Caches this information for optimization purposes.
  * Caches this information for optimization purposes.
  * Forces a recheck if force_check is true.
  * Forces a recheck if force_check is true.
+ *
  * @param bool $forceCheck = false
  * @param bool $forceCheck = false
  */
  */
 function is_not_banned($forceCheck = false)
 function is_not_banned($forceCheck = false)
@@ -491,6 +494,7 @@ function banPermissions()
  * Log a ban in the database.
  * Log a ban in the database.
  * Log the current user in the ban logs.
  * Log the current user in the ban logs.
  * Increment the hit counters for the specified ban ID's (if any.)
  * Increment the hit counters for the specified ban ID's (if any.)
+ *
  * @param array $ban_ids = array()
  * @param array $ban_ids = array()
  * @param string $email = null
  * @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.
  * Checks if a given email address might be banned.
  * Check if a given email is banned.
  * Check if a given email is banned.
  * Performs an immediate ban if the turns turns out positive.
  * Performs an immediate ban if the turns turns out positive.
+ *
  * @param string $email
  * @param string $email
  * @param string $restriction
  * @param string $restriction
  * @param string $error
  * @param string $error
@@ -594,6 +599,7 @@ function isBannedEmail($email, $restriction, $error)
  * Depends on the disableCheckUA setting, which is usually missing.
  * Depends on the disableCheckUA setting, which is usually missing.
  * Will check GET, POST, or REQUEST depending on the passed type.
  * 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.)
  * 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 $type = 'post' (post, get, request)
  * @param string $from_action = ''
  * @param string $from_action = ''
  * @param bool $is_fatal = true
  * @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.
  * Check if a specific confirm parameter was given.
+ *
  * @param string $action
  * @param string $action
  */
  */
 function checkConfirm($action)
 function checkConfirm($action)
@@ -727,6 +734,7 @@ function checkConfirm($action)
 
 
 /**
 /**
  * Lets give you a token of our appreciation.
  * Lets give you a token of our appreciation.
+ *
  * @param string $action
  * @param string $action
  * @param string $type = 'post'
  * @param string $type = 'post'
  * @return array
  * @return array
@@ -756,7 +764,7 @@ function createToken($action, $type = 'post')
  */
  */
 function validateToken($action, $type = 'post', $reset = true)
 function validateToken($action, $type = 'post', $reset = true)
 {
 {
-	global $modSettings, $sourcedir;
+	global $modSettings;
 
 
 	$type = $type == 'get' || $type == 'request' ? $type : 'post';
 	$type = $type == 'get' || $type == 'request' ? $type : 'post';
 
 
@@ -798,13 +806,6 @@ function validateToken($action, $type = 'post', $reset = true)
 		// I'm back baby.
 		// I'm back baby.
 		createToken($action, $type);
 		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);
 		fatal_lang_error('token_verify_fail', false);
 	}
 	}
 	// Remove this token as its useless
 	// 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
  * @param bool $complete = false
  */
  */
 function cleanTokens($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.)
  * checks whether the user is allowed to do permission. (ie. post_new.)
  * If boards is specified, checks those boards instead of the current one.
  * If boards is specified, checks those boards instead of the current one.
  * Always returns true if the user is an administrator.
  * Always returns true if the user is an administrator.
+ *
  * @param string $permission
  * @param string $permission
  * @param array $boards = null
  * @param array $boards = null
  * @return bool if the user can do the permission
  * @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.
  * 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 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().
  * If they are a guest and cannot do it, this calls is_not_guest().
+ *
  * @param string $permission
  * @param string $permission
  * @param array $boards = null
  * @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.)
  * 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.
  * If check_access is true will also make sure the group has proper access to that board.
+ *
  * @param array $permissions
  * @param array $permissions
  * @param bool $check_access = true
  * @param bool $check_access = true
  * @param bool $simple = 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
  *  'no_through_forum': don't show the email address, but do allow
  *    things to be mailed using the built-in forum mailer.
  *    things to be mailed using the built-in forum mailer.
  *  'no': keep the email address hidden.
  *  'no': keep the email address hidden.
+ *
  * @param bool $userProfile_hideEmail
  * @param bool $userProfile_hideEmail
  * @param int $userProfile_id
  * @param int $userProfile_id
  * @return string (yes, yes_permission_override, no_through_forum, no)
  * @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
  * A generic function to create a pair of index.php and .htaccess files in a directory
+ *
  * @param string $path, the (absolute) directory path
  * @param string $path, the (absolute) directory path
  * @param boolean $attachments, if the directory is an attachments directory or not
  * @param boolean $attachments, if the directory is an attachments directory or not
  * @return true on success, error string if anything fails
  * @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
  * @param string $fullip An IP address either IPv6 or not
  * @return string A SQL condition
  * @return string A SQL condition
  */
  */

+ 1 - 1
Sources/SendTopic.php

@@ -6,7 +6,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Session.php

@@ -11,7 +11,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 8 - 8
Sources/SplitTopics.php

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

+ 5 - 5
Sources/Stats.php

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

+ 29 - 15
Sources/Subs-Admin.php

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

+ 33 - 18
Sources/Subs-Auth.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -17,7 +17,7 @@ if (!defined('SMF'))
 	die('Hacking attempt...');
 	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.
  * - password should be already encrypted with the cookie salt.
  * - logs the user out if id_member is zero.
  * - logs the user out if id_member is zero.
  * - sets the cookie and session to last the number of seconds specified by cookie_length.
  * - 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'
  * @param string $type = 'admin'
  */
  */
-function adminLogin($type = 'admin', $additionalToken = false)
+function adminLogin($type = 'admin')
 {
 {
 	global $context, $scripturl, $txt, $user_info, $user_settings;
 	global $context, $scripturl, $txt, $user_info, $user_settings;
 
 
@@ -230,9 +230,6 @@ function adminLogin($type = 'admin', $additionalToken = false)
 	foreach ($_POST as $k => $v)
 	foreach ($_POST as $k => $v)
 		$context['post_data'] .= adminLogin_outputPostVars($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.
 	// Now we'll use the admin_login sub template of the Login template.
 	$context['sub_template'] = 'admin_login';
 	$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.
  * if 'value' is an array, the function is called recursively.
  *
  *
  * @param string $key
  * @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.
  * - is used as a popup for searching members.
  * - uses sub template find_members of the Help template.
  * - uses sub template find_members of the Help template.
  * - also used to add members for PM's sent using wap2/imode protocol.
  * - 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.
  * - used by javascript to find members matching the request.
  */
  */
 function RequestMembers()
 function RequestMembers()
@@ -607,26 +604,44 @@ function resetPassword($memID, $username = null)
  * @param string $username
  * @param string $username
  * @return string Returns null if fine
  * @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?
 	// No name?!  How can you register with no name?
 	if ($username == '')
 	if ($username == '')
-		fatal_lang_error('need_username', false);
+		$errors[] = array('lang', 'need_username');
 
 
 	// Only these characters are permitted.
 	// 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)
 	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)
 	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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -53,8 +53,9 @@ function getBoardIndex($boardIndexOptions)
 			' . ($user_info['is_guest'] ? ' 1 AS is_read, 0 AS new_from,' : '
 			' . ($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'] ? '
 			(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,' : '')) . '
 			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'] ? '
 		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}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)
 			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}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}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}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']) ? '' : '
 		WHERE {query_see_board}' . (empty($boardIndexOptions['countChildPosts']) ? (empty($boardIndexOptions['base_level']) ? '' : '
 			AND b.child_level >= {int:child_level}') : '
 			AND b.child_level >= {int:child_level}') : '
 			AND b.child_level BETWEEN ' . $boardIndexOptions['base_level'] . ' AND ' . ($boardIndexOptions['base_level'] + 1)),
 			AND b.child_level BETWEEN ' . $boardIndexOptions['base_level'] . ' AND ' . ($boardIndexOptions['base_level'] + 1)),
@@ -102,7 +104,7 @@ function getBoardIndex($boardIndexOptions)
 					'boards' => array(),
 					'boards' => array(),
 					'new' => false
 					'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.
 			// 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
 		else
 			continue;
 			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.
 		// Prepare the subject, and make sure it's not too long.
 		censorText($row_board['subject']);
 		censorText($row_board['subject']);
 		$row_board['short_subject'] = shorten_subject($row_board['subject'], 24);
 		$row_board['short_subject'] = shorten_subject($row_board['subject'], 24);
@@ -247,6 +267,14 @@ function getBoardIndex($boardIndexOptions)
 			'topic' => $row_board['id_topic']
 			'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.
 		// Provide the href and link.
 		if ($row_board['subject'] != '')
 		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
  * This file is mainly concerned with minor tasks relating to boards, such as
  * marking them read, collapsing categories, or quick moderation.
  * marking them read, collapsing categories, or quick moderation.
- * 
+ *
  * Simple Machines Forum (SMF)
  * Simple Machines Forum (SMF)
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -17,50 +17,6 @@
 if (!defined('SMF'))
 if (!defined('SMF'))
 	die('Hacking attempt...');
 	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.
  * Mark a board or multiple boards read.
  *
  *
@@ -469,6 +425,8 @@ function getMsgMemberID($messageID)
 
 
 /**
 /**
  * Modify the settings and position of a board.
  * Modify the settings and position of a board.
+ * Used by ManageBoards.php to change the settings of a board.
+ *
  * @param int $board_id
  * @param int $board_id
  * @param array &$boardOptions
  * @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']])))
 	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');
 		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.
 	// All things that will be updated in the database will be in $boardUpdates.
 	$boardUpdates = array();
 	$boardUpdates = array();
@@ -662,6 +621,9 @@ function modifyBoard($board_id, &$boardOptions)
 		$boardUpdateParameters['num_posts'] = (int) $boardOptions['num_posts'];
 		$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).
 	// Do the updates (if any).
 	if (!empty($boardUpdates))
 	if (!empty($boardUpdates))
 		$request = $smcFunc['db_query']('', '
 		$request = $smcFunc['db_query']('', '
@@ -752,6 +714,10 @@ function modifyBoard($board_id, &$boardOptions)
 
 
 /**
 /**
  * Create a new board and set its properties and position.
  * 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
  * @param array $boardOptions
  * @return int The new board id
  * @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']))
 	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);
 		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.
 	// Set every optional value to its default value.
 	$boardOptions += array(
 	$boardOptions += array(
 		'posts_count' => true,
 		'posts_count' => true,
@@ -780,18 +744,22 @@ function createBoard($boardOptions)
 		'inherit_permissions' => true,
 		'inherit_permissions' => true,
 		'dont_log' => 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.
 	// Insert a board, the settings are dealt with later.
 	$smcFunc['db_insert']('',
 	$smcFunc['db_insert']('',
 		'{db_prefix}boards',
 		'{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')
 		array('id_board')
 	);
 	);
 	$board_id = $smcFunc['db_insert_id']('{db_prefix}boards', '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.
  * 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 $boards_to_remove
  * @param array $moveChildrenTo = null
  * @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()
 function reorderBoards()
 {
 {
@@ -1019,6 +995,8 @@ function reorderBoards()
 
 
 /**
 /**
  * Fixes the children of a board by setting their child_levels to new values.
  * 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 $parent
  * @param int $newLevel
  * @param int $newLevel
  * @param int $newParent
  * @param int $newParent
@@ -1179,6 +1157,8 @@ function getBoardTree()
 
 
 /**
 /**
  * Recursively get a list of boards.
  * Recursively get a list of boards.
+ * Used by getBoardTree 
+ *
  * @param array &$_boardList
  * @param array &$_boardList
  * @param array &$_tree
  * @param array &$_tree
  */
  */

+ 42 - 29
Sources/Subs-Calendar.php

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -172,7 +172,7 @@ function getEventRange($low_date, $high_date, $use_permissions = true)
 					'start_date' => $row['start_date'],
 					'start_date' => $row['start_date'],
 					'end_date' => $row['end_date'],
 					'end_date' => $row['end_date'],
 					'is_last' => false,
 					'is_last' => false,
-					'id_board' => $row['id_board'],	
+					'id_board' => $row['id_board'],
 					'href' => $row['id_board'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0',
 					'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>',
 					'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')),
 					'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['board'] = isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0;
 	$eventOptions['topic'] = isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 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!
 	// Insert the event!
 	$smcFunc['db_insert']('',
 	$smcFunc['db_insert']('',
 		'{db_prefix}calendar',
 		'{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')
 		array('id_event')
 	);
 	);
 
 
 	// Store the just inserted id_event for future reference.
 	// Store the just inserted id_event for future reference.
 	$eventOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}calendar', 'id_event');
 	$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.
 	// Update the settings to show something calendarish was updated.
 	updateSettings(array(
 	updateSettings(array(
@@ -904,7 +907,7 @@ function modifyEvent($event_id, &$eventOptions)
 
 
 	// Properly sanitize the title.
 	// Properly sanitize the title.
 	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
 	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
-	
+
 	// Scan the start date for validity and get its components.
 	// Scan the start date for validity and get its components.
 	if (($num_results = sscanf($eventOptions['start_date'], '%d-%d-%d', $year, $month, $day)) !== 3)
 	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);
 		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']))
 	if (!isset($eventOptions['end_date']))
 		$eventOptions['end_date'] = strftime('%Y-%m-%d', mktime(0, 0, 0, $month, $day, $year) + $eventOptions['span'] * 86400);
 		$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']('', '
 	$smcFunc['db_query']('', '
 		UPDATE {db_prefix}calendar
 		UPDATE {db_prefix}calendar
 		SET
 		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}',
 		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,
 			'id_event' => $event_id,
 		)
 		)
 	);
 	);
-	
+
 	call_integration_hook('integrate_remove_event', array($event_id));
 	call_integration_hook('integrate_remove_event', array($event_id));
 
 
 	updateSettings(array(
 	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.
  * This file contains the functions to add, modify, remove, collapse and expand categories.
- * 
+ *
  * Simple Machines Forum (SMF)
  * Simple Machines Forum (SMF)
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -20,7 +20,7 @@ if (!defined('SMF'))
  * Edit the position and properties of a category.
  * Edit the position and properties of a category.
  * general function to modify the settings and position 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.
  * used by ManageBoards.php to change the settings of a category.
- * 
+ *
  * @param int $category_id
  * @param int $category_id
  * @param array $catOptions
  * @param array $catOptions
  */
  */
@@ -31,7 +31,8 @@ function modifyCategory($category_id, $catOptions)
 	$catUpdates = array();
 	$catUpdates = array();
 	$catParameters = 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?
 	// Wanna change the categories position?
 	if (isset($catOptions['move_after']))
 	if (isset($catOptions['move_after']))
@@ -93,6 +94,9 @@ function modifyCategory($category_id, $catOptions)
 		$catParameters['is_collapsible'] = $catOptions['is_collapsible'] ? 1 : 0;
 		$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).
 	// Do the updates (if any).
 	if (!empty($catUpdates))
 	if (!empty($catUpdates))
 	{
 	{
@@ -117,7 +121,7 @@ function modifyCategory($category_id, $catOptions)
  * general function to create a new category and set its position.
  * general function to create a new category and set its position.
  * allows (almost) the same options as the modifyCat() function.
  * allows (almost) the same options as the modifyCat() function.
  * returns the ID of the newly created category.
  * returns the ID of the newly created category.
- * 
+ *
  * @param int $createCategory
  * @param int $createCategory
  * @param array $catOptions
  * @param array $catOptions
  */
  */
@@ -125,8 +129,6 @@ function createCategory($catOptions)
 {
 {
 	global $smcFunc;
 	global $smcFunc;
 
 
-	call_integration_hook('integrate_create_category', array(&$catOptions));
-
 	// Check required values.
 	// Check required values.
 	if (!isset($catOptions['cat_name']) || trim($catOptions['cat_name']) == '')
 	if (!isset($catOptions['cat_name']) || trim($catOptions['cat_name']) == '')
 		trigger_error('createCategory(): A category name is required', E_USER_ERROR);
 		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.
 	// Don't log an edit right after.
 	$catOptions['dont_log'] = true;
 	$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.
 	// Add the category to the database.
 	$smcFunc['db_insert']('',
 	$smcFunc['db_insert']('',
 		'{db_prefix}categories',
 		'{db_prefix}categories',
-		array(
-			'name' => 'string-48',
-		),
-		array(
-			$catOptions['cat_name'],
-		),
+		$cat_columns,
+		$cat_parameters,
 		array('id_cat')
 		array('id_cat')
 	);
 	);
 
 
@@ -163,14 +170,14 @@ function createCategory($catOptions)
 	return $category_id;
 	return $category_id;
 }
 }
 
 
-/** 
+/**
  * Remove one or more categories.
  * Remove one or more categories.
  * general function to delete 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.
  * 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.
  * if moveChildrenTo is set to null, all boards inside the given categorieswill be deleted.
  * deletes all information that's associated with the given categories.
  * deletes all information that's associated with the given categories.
  * updates the statistics to reflect the new situation.
  * updates the statistics to reflect the new situation.
- * 
+ *
  * @param array $categories_to_remove
  * @param array $categories_to_remove
  * @param int $moveChildrenTo = null
  * @param int $moveChildrenTo = null
  */
  */
@@ -246,12 +253,12 @@ function deleteCategories($categories, $moveBoardsTo = null)
 	reorderBoards();
 	reorderBoards();
 }
 }
 
 
-/** 
+/**
  * Collapse, expand or toggle one or more categories for one or more members.
  * 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.
  * if members is null, the category is collapsed/expanded for all members.
  * allows three changes to the status: 'expand', 'collapse' and 'toggle'.
  * allows three changes to the status: 'expand', 'collapse' and 'toggle'.
  * if check_collapsable is set, only category allowed to be collapsed, will be collapsed.
  * if check_collapsable is set, only category allowed to be collapsed, will be collapsed.
- * 
+ *
  * @param array $categories
  * @param array $categories
  * @param string $new_status
  * @param string $new_status
  * @param array $members = null
  * @param array $members = null

+ 1 - 1
Sources/Subs-Charset.php

@@ -5,7 +5,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

+ 1 - 1
Sources/Subs-Compat.php

@@ -10,7 +10,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

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

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

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

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1

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

@@ -7,7 +7,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -848,4 +848,4 @@ function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false
 		);
 		);
 
 
 	return strtr($string, $replacements);
 	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
  * This file contains those functions specific to the editing box and is
  * generally used for WYSIWYG type functionality.
  * generally used for WYSIWYG type functionality.
- * 
+ *
  * Simple Machines Forum (SMF)
  * Simple Machines Forum (SMF)
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -21,7 +21,7 @@ if (!defined('SMF'))
  * !!!Compatibility!!!
  * !!!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
  * 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.
  * Convert only the BBC that can be edited in HTML mode for the editor.
- * 
+ *
  * @param string $text
  * @param string $text
  * @param boolean $compat_mode if true will convert the text, otherwise not (default false)
  * @param boolean $compat_mode if true will convert the text, otherwise not (default false)
  * @return string
  * @return string
@@ -85,9 +85,9 @@ function bbc_to_html($text, $compat_mode = false)
  * !!!Compatibility!!!
  * !!!Compatibility!!!
  * This is no more needed, but to avoid break mods let's keep it
  * 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
  * Run it it shouldn't even hurt either, so let's not bother remove it
- * 
+ *
  * The harder one - wysiwyg to BBC!
  * The harder one - wysiwyg to BBC!
- * 
+ *
  * @param string $text
  * @param string $text
  * @return string
  * @return string
  */
  */
@@ -849,9 +849,9 @@ function html_to_bbc($text)
 /**
 /**
  * !!!Compatibility!!!
  * !!!Compatibility!!!
  * This is no more needed, but to avoid break mods let's keep it
  * This is no more needed, but to avoid break mods let's keep it
- * 
+ *
  * Returns an array of attributes associated with a tag.
  * Returns an array of attributes associated with a tag.
- * 
+ *
  * @param string $text
  * @param string $text
  * @return string
  * @return string
  */
  */
@@ -1332,7 +1332,7 @@ function loadLocale()
 	else
 	else
 		@ob_start();
 		@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']))
 	if (empty($txt['lang_locale']))
 		die();
 		die();
 
 
@@ -1361,7 +1361,7 @@ function loadLocale()
  *   message icons or a list of custom message icons retrieved from the database.
  *   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
  * - The board_id is needed for the custom message icons (which can be set for
  *   each board individually).
  *   each board individually).
- * 
+ *
  * @param int $board_id
  * @param int $board_id
  * @return array
  * @return array
  */
  */
@@ -1435,7 +1435,7 @@ function getMessageIcons($board_id)
 
 
 /**
 /**
  * Compatibility function - used in 1.1 for showing a post box.
  * Compatibility function - used in 1.1 for showing a post box.
- * 
+ *
  * @param string $msg
  * @param string $msg
  * @return string
  * @return string
  */
  */
@@ -1469,28 +1469,25 @@ function create_control_richedit($editorOptions)
 		$settings['smileys_url'] = $modSettings['smileys_url'] . '/' . $user_info['smiley_set'];
 		$settings['smileys_url'] = $modSettings['smileys_url'] . '/' . $user_info['smiley_set'];
 
 
 		// This really has some WYSIWYG stuff.
 		// 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')
 		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');
 		$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
 		if ($context['show_spellchecking'])
 		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.
 			// Some hidden information is needed in order to make the spell checking work.
 			if (!isset($_REQUEST['xml']))
 			if (!isset($_REQUEST['xml']))
@@ -1709,7 +1706,7 @@ function create_control_richedit($editorOptions)
 		}
 		}
 
 
 		$bbcodes_styles = '';
 		$bbcodes_styles = '';
-		$context['bbcodes_hanlders'] = '';
+		$context['bbcodes_handlers'] = '';
 		$context['bbc_toolbar'] = array();
 		$context['bbc_toolbar'] = array();
 		foreach ($context['bbc_tags'] as $row => $tagRow)
 		foreach ($context['bbc_tags'] as $row => $tagRow)
 		{
 		{
@@ -1730,7 +1727,7 @@ function create_control_richedit($editorOptions)
 			}';
 			}';
 						if (isset($tag['before']))
 						if (isset($tag['before']))
 						{
 						{
-							$context['bbcodes_hanlders'] = '
+							$context['bbcodes_handlers'] = '
 				$.sceditor.setCommand(
 				$.sceditor.setCommand(
 					' . javaScriptEscape($tag['code']) . ',
 					' . javaScriptEscape($tag['code']) . ',
 					function () {
 					function () {
@@ -2166,6 +2163,8 @@ function AutoSuggestHandler($checkRegistered = null)
 		'versions' => 'SMFVersions',
 		'versions' => 'SMFVersions',
 	);
 	);
 
 
+	call_integration_hook('integrate_autosuggest', array($searchTypes));
+
 	// If we're just checking the callback function is registered return true or false.
 	// If we're just checking the callback function is registered return true or false.
 	if ($checkRegistered != null)
 	if ($checkRegistered != null)
 		return isset($searchTypes[$checkRegistered]) && function_exists('AutoSuggest_Search_' . $checkRegistered);
 		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.
  * Search for a member - by real_name or member_name by default.
- * 
+ *
  * @return string
  * @return string
  */
  */
 function AutoSuggest_Search_Member()
 function AutoSuggest_Search_Member()
@@ -2282,4 +2281,4 @@ function AutoSuggest_Search_SMFVersions()
 			);
 			);
 
 
 	return $xml_data;
 	return $xml_data;
-}
+}

+ 7 - 7
Sources/Subs-Graphics.php

@@ -12,7 +12,7 @@
  *
  *
  * @package SMF
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @version 2.1 Alpha 1
@@ -65,7 +65,7 @@ function downloadAvatar($url, $memID, $max_width, $max_height)
 		array('id_attach')
 		array('id_attach')
 	);
 	);
 	$attachID = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
 	$attachID = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
-	
+
 	// Retain this globally in case the script wants it.
 	// Retain this globally in case the script wants it.
 	$modSettings['new_avatar_data'] = array(
 	$modSettings['new_avatar_data'] = array(
 		'id' => $attachID,
 		'id' => $attachID,
@@ -282,7 +282,7 @@ function checkImagick()
 function imageMemoryCheck($sizes)
 function imageMemoryCheck($sizes)
 {
 {
 	global $modSettings;
 	global $modSettings;
-	
+
 	// doing the old 'set it and hope' way?
 	// doing the old 'set it and hope' way?
 	if (empty($modSettings['attachment_thumb_memory']))
 	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
 	// 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
 	// simply a shortcut of 8bpp, 3 channels, 1.66 overhead
 	$needed_memory = ($sizes[0] * $sizes[1] * 5);
 	$needed_memory = ($sizes[0] * $sizes[1] * 5);
-	
+
 	// if we need more, lets try to get it
 	// if we need more, lets try to get it
 	return setMemoryLimit($needed_memory, true);
 	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.
 	// We can't get to the file.
 	else
 	else
 		$sizes = array(-1, -1, -1);
 		$sizes = array(-1, -1, -1);
-		
+
 	// See if we have -or- can get the needed memory for this operation
 	// See if we have -or- can get the needed memory for this operation
 	if (checkGD() && !imageMemoryCheck($sizes))
 	if (checkGD() && !imageMemoryCheck($sizes))
 		return false;
 		return false;
@@ -710,7 +710,7 @@ if (!function_exists('imagecreatefrombmp'))
 				for ($j = 0; $j < $scan_line_size; $x++)
 				for ($j = 0; $j < $scan_line_size; $x++)
 				{
 				{
 					$byte = ord($scan_line{$j++});
 					$byte = ord($scan_line{$j++});
-					
+
 					imagesetpixel($dst_img, $x, $y, $palette[(($byte) & 128) != 0]);
 					imagesetpixel($dst_img, $x, $y, $palette[(($byte) & 128) != 0]);
 					for ($shift = 1; $shift < 8; $shift++) {
 					for ($shift = 1; $shift < 8; $shift++) {
 						if (++$x < $info['width']) imagesetpixel($dst_img, $x, $y, $palette[(($byte << $shift) & 128) != 0]);
 						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
  * @package SMF
  * @author Simple Machines http://www.simplemachines.org
  * @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
  * @license http://www.simplemachines.org/about/smf/license.php BSD
  *
  *
  * @version 2.1 Alpha 1
  * @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((empty($listOptions['default_sort_col']) || isset($listOptions['columns'][$listOptions['default_sort_col']])));
 	assert((!isset($listOptions['form']) || isset($listOptions['form']['href'])));
 	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.
 	// All the context data will be easily accessible by using a reference.
 	$context[$listOptions['id']] = array();
 	$context[$listOptions['id']] = array();
 	$list_context = &$context[$listOptions['id']];
 	$list_context = &$context[$listOptions['id']];
@@ -151,9 +153,9 @@ function createList($listOptions)
 
 
 			// Allow for basic formatting.
 			// Allow for basic formatting.
 			if (!empty($column['data']['comma_format']))
 			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']))
 			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?
 			// Set a style class for this column?
 			if (isset($column['data']['class']))
 			if (isset($column['data']['class']))

Some files were not shown because too many files changed in this diff