Browse Source

Merge branch 'release-2.1' of git://github.com/SimpleMachines/SMF2.1.git into htmlspecialchars

Suki 11 years ago
parent
commit
48d42bd23b
100 changed files with 1472 additions and 1483 deletions
  1. 1 0
      .gitattributes
  2. 7 1
      README.md
  3. 41 22
      SSI.php
  4. 43 88
      Sources/Admin.php
  5. 1 1
      Sources/Display.php
  6. 26 20
      Sources/Drafts.php
  7. 1 0
      Sources/Errors.php
  8. 1 1
      Sources/Load.php
  9. 24 10
      Sources/ManageAttachments.php
  10. 25 4
      Sources/ManageBans.php
  11. 5 5
      Sources/ManageLanguages.php
  12. 427 0
      Sources/ManageMaintenance.php
  13. 2 2
      Sources/ManageMembergroups.php
  14. 26 29
      Sources/ManageNews.php
  15. 13 0
      Sources/ManagePaid.php
  16. 1 1
      Sources/ManagePermissions.php
  17. 11 0
      Sources/ManageScheduledTasks.php
  18. 82 5
      Sources/ManageServer.php
  19. 180 638
      Sources/ManageSettings.php
  20. 57 24
      Sources/ModerationCenter.php
  21. 1 1
      Sources/News.php
  22. 15 10
      Sources/PersonalMessage.php
  23. 10 10
      Sources/Post.php
  24. 2 2
      Sources/Profile-Modify.php
  25. 1 1
      Sources/Profile.php
  26. 3 3
      Sources/QueryString.php
  27. 20 18
      Sources/RemoveTopic.php
  28. 17 5
      Sources/ScheduledTasks.php
  29. 2 2
      Sources/Security.php
  30. 1 1
      Sources/SendTopic.php
  31. 38 34
      Sources/Stats.php
  32. 3 2
      Sources/Subs-Auth.php
  33. 1 1
      Sources/Subs-Boards.php
  34. 6 0
      Sources/Subs-Db-mysql.php
  35. 6 0
      Sources/Subs-Db-mysqli.php
  36. 6 0
      Sources/Subs-Db-postgresql.php
  37. 6 0
      Sources/Subs-Db-sqlite.php
  38. 6 0
      Sources/Subs-Db-sqlite3.php
  39. 71 51
      Sources/Subs-Editor.php
  40. 8 2
      Sources/Subs-Members.php
  41. 21 14
      Sources/Subs-Menu.php
  42. 2 2
      Sources/Subs-Post.php
  43. 4 7
      Sources/Subs.php
  44. 1 1
      Sources/Subscriptions-PayPal.php
  45. 2 1
      Sources/Themes.php
  46. 82 64
      Themes/default/Admin.template.php
  47. 2 2
      Themes/default/Errors.template.php
  48. 0 103
      Themes/default/GenericMenu.template.php
  49. 0 22
      Themes/default/ManageAttachments.template.php
  50. 3 4
      Themes/default/ManageNews.template.php
  51. 1 1
      Themes/default/ManagePermissions.template.php
  52. 26 1
      Themes/default/ManageScheduledTasks.template.php
  53. 47 62
      Themes/default/ModerationCenter.template.php
  54. 3 6
      Themes/default/PersonalMessage.template.php
  55. 1 1
      Themes/default/Post.template.php
  56. 5 19
      Themes/default/Profile.template.php
  57. 0 10
      Themes/default/Settings.template.php
  58. 20 1
      Themes/default/Stats.template.php
  59. 1 1
      Themes/default/Themes.template.php
  60. 0 2
      Themes/default/Wireless.template.php
  61. 26 8
      Themes/default/css/admin.css
  62. 27 133
      Themes/default/css/index.css
  63. 1 24
      Themes/default/css/rtl.css
  64. BIN
      Themes/default/images/admin/big/attachment.png
  65. BIN
      Themes/default/images/admin/big/ban.png
  66. BIN
      Themes/default/images/admin/big/boards.png
  67. BIN
      Themes/default/images/admin/big/calendar.png
  68. BIN
      Themes/default/images/admin/big/current_theme.png
  69. BIN
      Themes/default/images/admin/big/default.png
  70. BIN
      Themes/default/images/admin/big/drafts.png
  71. BIN
      Themes/default/images/admin/big/engines.png
  72. BIN
      Themes/default/images/admin/big/features.png
  73. BIN
      Themes/default/images/admin/big/languages.png
  74. BIN
      Themes/default/images/admin/big/logs.png
  75. BIN
      Themes/default/images/admin/big/mail.png
  76. BIN
      Themes/default/images/admin/big/maintain.png
  77. BIN
      Themes/default/images/admin/big/membergroups.png
  78. BIN
      Themes/default/images/admin/big/members.png
  79. BIN
      Themes/default/images/admin/big/modifications.png
  80. BIN
      Themes/default/images/admin/big/news.png
  81. BIN
      Themes/default/images/admin/big/packages.png
  82. BIN
      Themes/default/images/admin/big/paid.png
  83. BIN
      Themes/default/images/admin/big/permissions.png
  84. BIN
      Themes/default/images/admin/big/posts.png
  85. BIN
      Themes/default/images/admin/big/regcenter.png
  86. BIN
      Themes/default/images/admin/big/reports.png
  87. BIN
      Themes/default/images/admin/big/scheduled.png
  88. BIN
      Themes/default/images/admin/big/search.png
  89. BIN
      Themes/default/images/admin/big/security.png
  90. BIN
      Themes/default/images/admin/big/server.png
  91. BIN
      Themes/default/images/admin/big/smiley.png
  92. BIN
      Themes/default/images/admin/big/support.png
  93. BIN
      Themes/default/images/admin/big/themes.png
  94. BIN
      Themes/default/images/admin/drafts.png
  95. BIN
      Themes/default/images/admin/feature_cp.png
  96. BIN
      Themes/default/images/admin/feature_dr.png
  97. BIN
      Themes/default/images/admin/feature_ih.png
  98. BIN
      Themes/default/images/admin/feature_rg.png
  99. BIN
      Themes/default/images/admin/features_and_options.png
  100. BIN
      Themes/default/images/admin/forum_maintenance.png

+ 1 - 0
.gitattributes

@@ -8,3 +8,4 @@
 *.tgz binary
 *.zip binary
 *.tar.gz binary
+*.ttf binary

+ 7 - 1
README.md

@@ -30,5 +30,11 @@ by signing off your contributions, you acknowledge that you can and do license y
 * 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. ;)
+Please, feel free to play around. That's what we're doing. ;)
 
+######Security matters:
+
+Lastly, if you have a security issue you would like to notify us about regarding SMF - not just for 2.1, but for any version -
+please file a [security report](http://www.simplemachines.org/about/smf/security.php) on our website: http://www.simplemachines.org/about/smf/security.php
+
+This will enable the team to review it and prepare patches as appropriate before exploits are widely known, which helps keep everyone safe.

+ 41 - 22
SSI.php

@@ -451,35 +451,54 @@ function ssi_recentTopics($num_recent = 8, $exclude_boards = null, $include_boar
 	// Find all the posts in distinct topics.  Newer ones will have higher IDs.
 	$request = $smcFunc['db_query']('substring', '
 		SELECT
-			m.poster_time, ms.subject, m.id_topic, m.id_member, m.id_msg, b.id_board, b.name AS board_name, t.num_replies, t.num_views,
-			IFNULL(mem.real_name, m.poster_name) AS poster_name, ' . ($user_info['is_guest'] ? '1 AS is_read, 0 AS new_from' : '
-			IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) >= m.id_msg_modified AS is_read,
-			IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from') . ', SUBSTRING(m.body, 1, 384) AS body, m.smileys_enabled, m.icon
+			t.id_topic, b.id_board, b.name AS board_name
 		FROM {db_prefix}topics AS t
-			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
-			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
-			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
-			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 = t.id_topic AND lt.id_member = {int:current_member})
-			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = b.id_board AND lmr.id_member = {int:current_member})' : '') . '
-		WHERE t.id_last_msg >= {int:min_message_id}
-			' . (empty($exclude_boards) ? '' : '
-			AND b.id_board NOT IN ({array_int:exclude_boards})') . '
-			' . (empty($include_boards) ? '' : '
+			INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
+			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
+		WHERE t.id_last_msg >= {int:min_message_id}' . (empty($exclude_boards) ? '' : '
+			AND b.id_board NOT IN ({array_int:exclude_boards})') . '' . (empty($include_boards) ? '' : '
 			AND b.id_board IN ({array_int:include_boards})') . '
 			AND {query_wanna_see_board}' . ($modSettings['postmod_active'] ? '
 			AND t.approved = {int:is_approved}
-			AND m.approved = {int:is_approved}' : '') . '
+			AND ml.approved = {int:is_approved}' : '') . '
 		ORDER BY t.id_last_msg DESC
 		LIMIT ' . $num_recent,
 		array(
-			'current_member' => $user_info['id'],
 			'include_boards' => empty($include_boards) ? '' : $include_boards,
 			'exclude_boards' => empty($exclude_boards) ? '' : $exclude_boards,
 			'min_message_id' => $modSettings['maxMsgID'] - 35 * min($num_recent, 5),
 			'is_approved' => 1,
 		)
 	);
+	$topics = array();
+	while ($row = $smcFunc['db_fetch_assoc']($request))
+		$topics[$row['id_topic']] = $row;
+	$smcFunc['db_free_result']($request);
+
+	// Did we find anything? If not, bail.
+	if (empty($topics))
+		return array();
+
+	// Find all the posts in distinct topics.  Newer ones will have higher IDs.
+	$request = $smcFunc['db_query']('substring', '
+		SELECT
+			mf.poster_time, mf.subject, ml.id_topic, mf.id_member, ml.id_msg, t.num_replies, t.num_views, mg.online_color,
+			IFNULL(mem.real_name, mf.poster_name) AS poster_name, ' . ($user_info['is_guest'] ? '1 AS is_read, 0 AS new_from' : '
+			IFNULL(lt.id_msg, IFNULL(lmr.id_msg, 0)) >= ml.id_msg_modified AS is_read,
+			IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from') . ', SUBSTRING(mf.body, 1, 384) AS body, mf.smileys_enabled, mf.icon
+		FROM {db_prefix}topics AS t
+			INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
+			INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_last_msg)
+			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mf.id_member)' . (!$user_info['is_guest'] ? '
+			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
+			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})' : '') . '
+			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)
+		WHERE t.id_topic IN ({array_int:topic_list})',
+		array(
+			'current_member' => $user_info['id'],
+			'topic_list' => array_keys($topics),
+		)
+	);
 	$posts = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
 	{
@@ -497,10 +516,10 @@ function ssi_recentTopics($num_recent = 8, $exclude_boards = null, $include_boar
 		// Build the array.
 		$posts[] = array(
 			'board' => array(
-				'id' => $row['id_board'],
-				'name' => $row['board_name'],
-				'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
-				'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['board_name'] . '</a>'
+				'id' => $topics[$row['id_topic']]['id_board'],
+				'name' => $topics[$row['id_topic']]['board_name'],
+				'href' => $scripturl . '?board=' . $topics[$row['id_topic']]['id_board'] . '.0',
+				'link' => '<a href="' . $scripturl . '?board=' . $topics[$row['id_topic']]['id_board'] . '.0">' . $topics[$row['id_topic']]['board_name'] . '</a>',
 			),
 			'topic' => $row['id_topic'],
 			'poster' => array(
@@ -832,7 +851,7 @@ function ssi_fetchGroupMembers($group_id = null, $output_method = 'echo')
 	$query_where = '
 		id_group = {int:id_group}
 		OR id_post_group = {int:id_group}
-		OR FIND_IN_SET({int:id_group}, additional_groups)';
+		OR FIND_IN_SET({int:id_group}, additional_groups) != 0';
 
 	$query_where_params = array(
 		'id_group' => $group_id,
@@ -1651,7 +1670,7 @@ function ssi_boardNews($board = null, $limit = null, $start = null, $length = nu
 		SELECT id_board
 		FROM {db_prefix}boards
 		WHERE ' . ($board === null ? '' : 'id_board = {int:current_board}
-			AND ') . 'FIND_IN_SET(-1, member_groups)
+			AND ') . 'FIND_IN_SET(-1, member_groups) != 0
 		LIMIT 1',
 		array(
 			'current_board' => $board,

+ 43 - 88
Sources/Admin.php

@@ -25,13 +25,16 @@ if (!defined('SMF'))
  */
 function AdminMain()
 {
-	global $txt, $context, $scripturl, $modSettings, $settings, $sourcedir, $options, $boarddir;
+	global $txt, $context, $scripturl, $modSettings, $settings, $sourcedir, $options, $boarddir, $maintenance;
 
 	// Load the language and templates....
 	loadLanguage('Admin');
 	loadTemplate('Admin', 'admin');
 	loadJavascriptFile('admin.js', array('default_theme' => true), 'admin.js');
 
+	if (!empty($maintenance))
+		$context['template_layers'][] = 'maint_warning';
+
 	// No indexing evil stuff.
 	$context['robot_no_index'] = true;
 
@@ -116,7 +119,7 @@ function AdminMain()
 						'layout' => array($txt['mods_cat_layout']),
 						'karma' => array($txt['karma'], 'enabled' => in_array('k', $context['admin_features'])),
 						'sig' => array($txt['signature_settings_short']),
-						'profile' => array($txt['custom_profile_shorttitle'], 'enabled' => in_array('cp', $context['admin_features'])),
+						'profile' => array($txt['custom_profile_shorttitle']),
 					),
 				),
 				'securitysettings' => array(
@@ -125,7 +128,6 @@ function AdminMain()
 					'function' => 'ModifySecuritySettings',
 					'icon' => 'security.png',
 					'subsections' => array(
-						'general' => array($txt['mods_cat_security_general']),
 						'spam' => array($txt['antispam_title']),
 						'moderation' => array($txt['moderation_settings_short'], 'enabled' => in_array('w', $context['admin_features'])),
 					),
@@ -141,20 +143,6 @@ function AdminMain()
 						'settings' => array($txt['language_settings']),
 					),
 				),
-				'serversettings' => array(
-					'label' => $txt['admin_server_settings'],
-					'file' => 'ManageServer.php',
-					'function' => 'ModifySettings',
-					'icon' => 'server.png',
-					'subsections' => array(
-						'general' => array($txt['general_settings']),
-						'database' => array($txt['database_paths_settings']),
-						'cookie' => array($txt['cookies_sessions_settings']),
-						'cache' => array($txt['caching_settings']),
-						'loads' => array($txt['load_balancing_settings']),
-						'phpinfo' => array($txt['phpinfo_settings']),
-					),
-				),
 				'current_theme' => array(
 					'label' => $txt['theme_current_settings'],
 					'file' => 'Themes.php',
@@ -182,7 +170,6 @@ function AdminMain()
 					'icon' => 'modifications.png',
 					'subsections' => array(
 						'general' => array($txt['mods_cat_modifications_misc']),
-						'hooks' => array($txt['hooks_title_list']),
 						// Mod Authors for a "ADD AFTER" on this line. Ensure you end your change with a comma. For example:
 						// 'shout' => array($txt['shout']),
 						// Note the comma!! The setting with automatically appear with the first mod to be added.
@@ -223,9 +210,8 @@ function AdminMain()
 					'label' => $txt['manage_drafts'],
 					'file' => 'Drafts.php',
 					'function' => 'ModifyDraftSettings',
-					'icon' => 'logs.png',
+					'icon' => 'drafts.png',
 					'permission' => array('admin_forum'),
-					'enabled' => in_array('dr', $context['admin_features']),
 				),
 				'managecalendar' => array(
 					'label' => $txt['manage_calendar'],
@@ -280,6 +266,20 @@ function AdminMain()
 						'maintenance' => array($txt['attachment_manager_maintenance']),
 					),
 				),
+				'sengines' => array(
+					'label' => $txt['search_engines'],
+					'enabled' => in_array('sp', $context['admin_features']),
+					'file' => 'ManageSearchEngines.php',
+					'icon' => 'engines.png',
+					'function' => 'SearchEngines',
+					'permission' => 'admin_forum',
+					'subsections' => array(
+						'stats' => array($txt['spider_stats']),
+						'logs' => array($txt['spider_logs']),
+						'spiders' => array($txt['spiders']),
+						'settings' => array($txt['settings']),
+					),
+				),
 			),
 		),
 		'members' => array(
@@ -361,26 +361,27 @@ function AdminMain()
 						'settings' => array($txt['settings']),
 					),
 				),
-				'sengines' => array(
-					'label' => $txt['search_engines'],
-					'enabled' => in_array('sp', $context['admin_features']),
-					'file' => 'ManageSearchEngines.php',
-					'icon' => 'engines.png',
-					'function' => 'SearchEngines',
-					'permission' => 'admin_forum',
-					'subsections' => array(
-						'stats' => array($txt['spider_stats']),
-						'logs' => array($txt['spider_logs']),
-						'spiders' => array($txt['spiders']),
-						'settings' => array($txt['settings']),
-					),
-				),
 			),
 		),
 		'maintenance' => array(
 			'title' => $txt['admin_maintenance'],
 			'permission' => array('admin_forum'),
 			'areas' => array(
+				'serversettings' => array(
+					'label' => $txt['admin_server_settings'],
+					'file' => 'ManageServer.php',
+					'function' => 'ModifySettings',
+					'icon' => 'server.png',
+					'subsections' => array(
+						'general' => array($txt['general_settings']),
+						'database' => array($txt['database_paths_settings']),
+						'cookie' => array($txt['cookies_sessions_settings']),
+						'security' => array($txt['security_settings']),
+						'cache' => array($txt['caching_settings']),
+						'loads' => array($txt['load_balancing_settings']),
+						'phpinfo' => array($txt['phpinfo_settings']),
+					),
+				),
 				'maintain' => array(
 					'label' => $txt['maintain_title'],
 					'file' => 'ManageMaintenance.php',
@@ -391,6 +392,7 @@ function AdminMain()
 						'database' => array($txt['maintain_sub_database'], 'admin_forum'),
 						'members' => array($txt['maintain_sub_members'], 'admin_forum'),
 						'topics' => array($txt['maintain_sub_topics'], 'admin_forum'),
+						'hooks' => array($txt['hooks_title_list'], 'admin_forum'),
 					),
 				),
 				'scheduledtasks' => array(
@@ -414,7 +416,6 @@ function AdminMain()
 					),
 				),
 				'reports' => array(
-					'enabled' => in_array('rg', $context['admin_features']),
 					'label' => $txt['generate_reports'],
 					'file' => 'Reports.php',
 					'function' => 'ReportsMain',
@@ -431,7 +432,7 @@ function AdminMain()
 						'banlog' => array($txt['ban_log'], 'manage_bans'),
 						'spiderlog' => array($txt['spider_logs'], 'admin_forum', 'enabled' => in_array('sp', $context['admin_features'])),
 						'tasklog' => array($txt['scheduled_log'], 'admin_forum'),
-						'pruning' => array($txt['pruning_title'], 'admin_forum'),
+						'settings' => array($txt['log_settings'], 'admin_forum'),
 					),
 				),
 				'repairboards' => array(
@@ -461,7 +462,7 @@ function AdminMain()
 	validateSession();
 
 	// Actually create the menu!
-	$admin_include_data = createMenu($admin_areas);
+	$admin_include_data = createMenu($admin_areas, array('do_big_icons' => true));
 	unset($admin_areas);
 
 	// Nothing valid?
@@ -561,52 +562,6 @@ function AdminHome()
 				' . sprintf($txt['admin_main_welcome'], $txt['admin_center'], $txt['help'], $txt['help']),
 		);
 
-
-	// The format of this array is: permission, action, title, description, icon.
-	$quick_admin_tasks = array(
-		array('', 'credits', 'support_credits_title', 'support_credits_info', 'support_and_credits.png'),
-		array('admin_forum', 'featuresettings', 'modSettings_title', 'modSettings_info', 'features_and_options.png'),
-		array('admin_forum', 'maintain', 'maintain_title', 'maintain_info', 'forum_maintenance.png'),
-		array('manage_permissions', 'permissions', 'edit_permissions', 'edit_permissions_info', 'permissions_lg.png'),
-		array('admin_forum', 'theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id'], 'theme_admin', 'theme_admin_info', 'themes_and_layout.png'),
-		array('admin_forum', 'packages', 'package', 'package_info', 'packages_lg.png'),
-		array('manage_smileys', 'smileys', 'smileys_manage', 'smileys_manage_info', 'smilies_and_messageicons.png'),
-		array('moderate_forum', 'viewmembers', 'admin_users', 'member_center_info', 'members_lg.png'),
-	);
-
-	$context['quick_admin_tasks'] = array();
-	foreach ($quick_admin_tasks as $task)
-	{
-		if (!empty($task[0]) && !allowedTo($task[0]))
-			continue;
-
-		$context['quick_admin_tasks'][] = array(
-			'href' => $scripturl . '?action=admin;area=' . $task[1],
-			'link' => '<a href="' . $scripturl . '?action=admin;area=' . $task[1] . '">' . $txt[$task[2]] . '</a>',
-			'title' => $txt[$task[2]],
-			'description' => $txt[$task[3]],
-			'icon' => $task[4],
-			'is_last' => false
-		);
-	}
-
-	if (count($context['quick_admin_tasks']) % 2 == 1)
-	{
-		$context['quick_admin_tasks'][] = array(
-			'href' => '',
-			'link' => '',
-			'title' => '',
-			'description' => '',
-			'is_last' => true
-		);
-		$context['quick_admin_tasks'][count($context['quick_admin_tasks']) - 2]['is_last'] = true;
-	}
-	elseif (count($context['quick_admin_tasks']) != 0)
-	{
-		$context['quick_admin_tasks'][count($context['quick_admin_tasks']) - 1]['is_last'] = true;
-		$context['quick_admin_tasks'][count($context['quick_admin_tasks']) - 2]['is_last'] = true;
-	}
-
 	// Lastly, fill in the blanks in the support resources paragraphs.
 	$txt['support_resources_p1'] = sprintf($txt['support_resources_p1'],
 		'http://wiki.simplemachines.org/',
@@ -741,7 +696,6 @@ function AdminSearchInternal()
 		array('ModifyLayoutSettings', 'area=featuresettings;sa=layout'),
 		array('ModifyKarmaSettings', 'area=featuresettings;sa=karma'),
 		array('ModifySignatureSettings', 'area=featuresettings;sa=sig'),
-		array('ModifyGeneralSecuritySettings', 'area=securitysettings;sa=general'),
 		array('ModifySpamSettings', 'area=securitysettings;sa=spam'),
 		array('ModifyModerationSettings', 'area=securitysettings;sa=moderation'),
 		array('ModifyGeneralModSettings', 'area=modsettings;sa=general'),
@@ -761,12 +715,13 @@ function AdminSearchInternal()
 		array('ModifyGeneralSettings', 'area=serversettings;sa=general'),
 		array('ModifyDatabaseSettings', 'area=serversettings;sa=database'),
 		array('ModifyCookieSettings', 'area=serversettings;sa=cookie'),
+		array('ModifyGeneralSecuritySettings', 'area=serversettings;sa=security'),
 		array('ModifyCacheSettings', 'area=serversettings;sa=cache'),
 		array('ModifyLanguageSettings', 'area=languages;sa=settings'),
 		array('ModifyRegistrationSettings', 'area=regcenter;sa=settings'),
 		array('ManageSearchEngineSettings', 'area=sengines;sa=settings'),
 		array('ModifySubscriptionSettings', 'area=paidsubscribe;sa=settings'),
-		array('ModifyPruningSettings', 'area=logs;sa=pruning'),
+		array('ModifyLogSettings', 'area=logs;sa=settings'),
 	);
 
 	call_integration_hook('integrate_admin_search', array(&$language_files, &$include_files, &$settings_search));
@@ -944,7 +899,7 @@ function AdminLogs()
 		'banlog' => array('ManageBans.php', 'BanLog'),
 		'spiderlog' => array('ManageSearchEngines.php', 'SpiderLogs'),
 		'tasklog' => array('ManageScheduledTasks.php', 'TaskLog'),
-		'pruning' => array('ManageSettings.php', 'ModifyPruningSettings'),
+		'settings' => array('ManageSettings.php', 'ModifyLogSettings'),
 	);
 
 	call_integration_hook('integrate_manage_logs', array(&$log_functions));
@@ -979,8 +934,8 @@ function AdminLogs()
 			'tasklog' => array(
 				'description' => $txt['scheduled_log_desc'],
 			),
-			'pruning' => array(
-				'description' => $txt['pruning_log_desc'],
+			'settings' => array(
+				'description' => $txt['log_settings_desc'],
 			),
 		),
 	);

+ 1 - 1
Sources/Display.php

@@ -1115,7 +1115,7 @@ function Display()
 	$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_save'] = !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');

+ 26 - 20
Sources/Drafts.php

@@ -33,7 +33,7 @@ function SaveDraft(&$post_errors)
 	global $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']))
+	if (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
@@ -183,7 +183,7 @@ 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']))
+	if (empty($modSettings['drafts_pm_enabled']) || !allowedTo('pm_draft') || !isset($_POST['save_draft']))
 		return false;
 
 	// read in what you sent us
@@ -213,7 +213,6 @@ function SavePMDraft(&$post_errors, $recipientList)
 
 	// 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" => ''));
 
@@ -232,8 +231,7 @@ function SavePMDraft(&$post_errors, $recipientList)
 				poster_time = {int:poster_time},
 				subject = {string:subject},
 				body = {string:body},
-				to_list = {string:to_list},
-				outbox = {int:outbox}
+				to_list = {string:to_list}
 			WHERE id_draft = {int:id_pm_draft}
 			LIMIT 1',
 			array(
@@ -244,7 +242,6 @@ function SavePMDraft(&$post_errors, $recipientList)
 				'body' => $draft['body'],
 				'id_pm_draft' => $id_pm_draft,
 				'to_list' => serialize($recipientList),
-				'outbox' => $outbox,
 			)
 		);
 
@@ -265,7 +262,6 @@ function SavePMDraft(&$post_errors, $recipientList)
 				'subject' => 'string-255',
 				'body' => 'string-65534',
 				'to_list' => 'string-255',
-				'outbox' => 'int',
 			),
 			array(
 				$reply_id,
@@ -275,7 +271,6 @@ function SavePMDraft(&$post_errors, $recipientList)
 				$draft['subject'],
 				$draft['body'],
 				serialize($recipientList),
-				$outbox,
 			),
 			array(
 				'id_draft'
@@ -373,7 +368,6 @@ function ReadDraft($id_draft, $type = 0, $check = true, $load = false)
 		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;
@@ -745,7 +739,7 @@ function showPMDrafts($memID = -1)
 	// 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
+			ud.id_member, ud.id_draft, ud.body, ud.subject, ud.poster_time, 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']) ? '
@@ -848,7 +842,7 @@ function showPMDrafts($memID = -1)
  */
 function ModifyDraftSettings($return_config = false)
 {
-	global $context, $txt, $sourcedir, $scripturl;
+	global $context, $txt, $sourcedir, $scripturl, $smcFunc;
 
 	isAllowedTo('admin_forum');
 
@@ -885,23 +879,35 @@ function ModifyDraftSettings($return_config = false)
 		checkSession();
 
 		// Protect them from themselves.
-		$_POST['drafts_autosave_frequency'] = $_POST['drafts_autosave_frequency'] < 30 ? 30 : $_POST['drafts_autosave_frequency'];
+		$_POST['drafts_autosave_frequency'] = !isset($_POST['drafts_autosave_frequency']) || $_POST['drafts_autosave_frequency'] < 30 ? 30 : $_POST['drafts_autosave_frequency'];
+
+		// Also disable the scheduled task if we're not using it.
+		$smcFunc['db_query']('', '
+			UPDATE {db_prefix}scheduled_tasks
+			SET disabled = {int:disabled}
+			WHERE task = {string:task}',
+			array(
+				'disabled' => !empty($_POST['drafts_keep_days']) ? 0 : 1,
+				'task' => 'remove_old_drafts',
+			)
+		);
+		require_once($sourcedir . '/ScheduledTasks.php');
+		CalculateNextTrigger();
+
+		// Save everything else and leave.
 		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;
-		}
+			$("#drafts_autosave_frequency").prop("disabled", !($("#drafts_autosave_enabled").prop("checked")));
+		};
+		toggle();
+
+		$("#drafts_autosave_enabled").click(function() { toggle(); });
 	';
 
 	// Final settings...

+ 1 - 0
Sources/Errors.php

@@ -79,6 +79,7 @@ function log_error($error_message, $error_type = 'general', $file = null, $line
 		'database',
 		'undefined_vars',
 		'user',
+		'ban',
 		'template',
 		'debug',
 	);

+ 1 - 1
Sources/Load.php

@@ -1797,7 +1797,7 @@ function loadTheme($id_theme = 0, $initialize = true)
 		$modSettings['memberCount'] = $modSettings['totalMembers'];
 
 	// This allows us to change the way things look for the admin.
-	$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,k,w,ml,pm');
 
 	// Default JS variables for use in every theme
 	$context['javascript_vars'] = array(

+ 24 - 10
Sources/ManageAttachments.php

@@ -135,9 +135,6 @@ function ManageAttachmentSettings($return_config = false)
 		array('title', 'attachment_manager_settings'),
 			// Are attachments enabled?
 			array('select', 'attachmentEnable', array($txt['attachmentEnable_deactivate'], $txt['attachmentEnable_enable_all'], $txt['attachmentEnable_disable_new'])),
-		'',
-			// Extension checks etc.
-			array('check', 'attachmentRecodeLineEndings'),
 		'',
 			// Directory and size limits.
 			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'])),
@@ -170,7 +167,7 @@ function ManageAttachmentSettings($return_config = false)
 			array('check', 'attachmentShowImages'),
 			array('check', 'attachmentThumbnails'),
 			array('check', 'attachment_thumb_png'),
-			array('check', 'attachment_thumb_memory', 'subtext' => $txt['attachment_thumb_memory_note1'], 'postinput' => $txt['attachment_thumb_memory_note2']),
+			array('check', 'attachment_thumb_memory'),
 			array('warning', 'attachment_thumb_memory_note'),
 			array('text', 'attachmentThumbWidth', 6),
 			array('text', 'attachmentThumbHeight', 6),
@@ -363,23 +360,37 @@ function ManageAvatarSettings($return_config = false)
  *  and ?action=admin;area=manageattachments;sa=browse;avatars for avatars.
  * Allows sorting by name, date, size and member.
  * Paginates results.
- *
- *  @uses the 'browse' sub template
  */
 function BrowseFiles()
 {
 	global $context, $txt, $scripturl, $options, $modSettings;
-	global $smcFunc, $sourcedir;
-
-	$context['sub_template'] = 'browse';
+	global $smcFunc, $sourcedir, $settings;
 
 	// Attachments or avatars?
 	$context['browse_type'] = isset($_REQUEST['avatars']) ? 'avatars' : (isset($_REQUEST['thumbs']) ? 'thumbs' : 'attachments');
 
+	$titles = array(
+		'attachments' => array('?action=admin;area=manageattachments;sa=browse', $txt['attachment_manager_attachments']),
+		'avatars' => array('?action=admin;area=manageattachments;sa=browse;avatars', $txt['attachment_manager_avatars']),
+		'thumbs' => array('?action=admin;area=manageattachments;sa=browse;thumbs', $txt['attachment_manager_thumbs']),
+	);
+
+	$list_title = $txt['attachment_manager_browse_files'] . ': ';
+	foreach ($titles as $browse_type => $details)
+	{
+		if ($browse_type != 'attachments')
+			$list_title .= ' | ';
+
+		if ($context['browse_type'] == $browse_type)
+			$list_title .= '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;" /> ';
+
+		$list_title .= '<a href="' . $scripturl . $details[0] . '">' . $details[1] . '</a>';
+	}
+
 	// Set the options for the list component.
 	$listOptions = array(
 		'id' => 'file_list',
-		'title' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ($context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments'))],
+		'title' => $list_title,
 		'items_per_page' => $modSettings['defaultMaxMessages'],
 		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
 		'default_sort_col' => 'name',
@@ -548,6 +559,9 @@ function BrowseFiles()
 	// Create the list.
 	require_once($sourcedir . '/Subs-List.php');
 	createList($listOptions);
+
+	$context['sub_template'] = 'show_list';
+	$context['default_list'] = 'file_list';
 }
 
 /**

+ 25 - 4
Sources/ManageBans.php

@@ -536,6 +536,27 @@ function BanEdit()
 					$context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']);
 				}
 			}
+
+			// We come from the mod center.
+			elseif(isset($_GET['msg']) && !empty($_GET['msg']))
+			{
+				$request = $smcFunc['db_query']('', '
+					SELECT poster_name, poster_ip, poster_email
+					FROM {db_prefix}messages
+					WHERE id_msg = {int:message}
+					LIMIT 1',
+					array(
+						'message' => (int) $_REQUEST['msg'],
+					)
+				);
+				if ($smcFunc['db_num_rows']($request) > 0)
+					list ($context['ban_suggestions']['member']['name'], $context['ban_suggestions']['main_ip'], $context['ban_suggestions']['email']) = $smcFunc['db_fetch_row']($request);
+				$smcFunc['db_free_result']($request);
+
+				// Can't hurt to ban base on the guest name...
+				$context['ban']['name'] = $context['ban_suggestions']['member']['name'];
+				$context['ban']['from_user'] = true;
+			}
 		}
 	}
 
@@ -938,9 +959,9 @@ function removeBanTriggers($items_ids = array(), $group_id = false)
 			{
 				$ban_items[$row['id_ban']]['type'] = 'ip';
 				$ban_items[$row['id_ban']]['ip'] = range2ip(array($row['ip_low1'], $row['ip_low2'], $row['ip_low3'], $row['ip_low4'] ,$row['ip_low5'], $row['ip_low6'], $row['ip_low7'], $row['ip_low8']), array($row['ip_high1'], $row['ip_high2'], $row['ip_high3'], $row['ip_high4'], $row['ip_high5'], $row['ip_high6'], $row['ip_high7'], $row['ip_high8']));
-				
-				$is_range = (strpos($ban_items[$row['id_ban']]['ip'], '-') !== false || strpos($ban_items[$row['id_ban']]['ip'], '*') !== false); 
-				
+
+				$is_range = (strpos($ban_items[$row['id_ban']]['ip'], '-') !== false || strpos($ban_items[$row['id_ban']]['ip'], '*') !== false);
+
 				$log_info[] = array(
 					'bantype' => ($is_range ? 'ip_range' : 'main_ip'),
 					'value' => $ban_items[$row['id_ban']]['ip'],
@@ -987,7 +1008,7 @@ function removeBanTriggers($items_ids = array(), $group_id = false)
 	$smcFunc['db_free_result']($request);
 
 	if ($group_id !== false)
-	{		
+	{
 		$smcFunc['db_query']('', '
 			DELETE FROM {db_prefix}ban_items
 			WHERE id_ban IN ({array_int:ban_list})

+ 5 - 5
Sources/ManageLanguages.php

@@ -973,7 +973,7 @@ function ModifyLanguage()
 		// Members can no longer use this language.
 		$smcFunc['db_query']('', '
 			UPDATE {db_prefix}members
-			SET lngfile = {string:empty_string}
+			SET lngfile = {empty}
 			WHERE lngfile = {string:current_language}',
 			array(
 				'empty_string' => '',
@@ -1011,10 +1011,10 @@ function ModifyLanguage()
 		$current_data = implode('', file($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'));
 		// These are the replacements. old => new
 		$replace_array = array(
-			'~\$txt\[\'lang_character_set\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_character_set\'] = \'' . addslashes($_POST['character_set']) . '\';',
-			'~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . addslashes($_POST['locale']) . '\';',
-			'~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . addslashes($_POST['dictionary']) . '\';',
-			'~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . addslashes($_POST['spelling']) . '\';',
+			'~\$txt\[\'lang_character_set\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_character_set\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['character_set']) . '\';',
+			'~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['locale']) . '\';',
+			'~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['dictionary']) . '\';',
+			'~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['spelling']) . '\';',
 			'~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';',
 		);
 		$current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);

+ 427 - 0
Sources/ManageMaintenance.php

@@ -84,6 +84,9 @@ function ManageMaintenance()
 				'olddrafts' => 'MaintainRemoveOldDrafts',
 			),
 		),
+		'hooks' => array(
+			'function' => 'list_integration_hooks',
+		),
 		'destroy' => array(
 			'function' => 'Destroy',
 			'activities' => array(),
@@ -2126,4 +2129,428 @@ function MaintainRecountPosts()
 	redirectexit('action=admin;area=maintain;sa=members;done=recountposts');
 }
 
+/**
+ * Generates a list of integration hooks for display
+ * Accessed through ?action=admin;area=maintain;sa=hooks;
+ * Allows for removal or disabing of selected hooks
+ */
+function list_integration_hooks()
+{
+	global $sourcedir, $scripturl, $context, $txt, $modSettings, $settings;
+
+	$context['filter_url'] = '';
+	$context['current_filter'] = '';
+	$currentHooks = get_integration_hooks();
+	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($currentHooks)))
+	{
+		$context['filter_url'] = ';filter=' . $_GET['filter'];
+		$context['current_filter'] = $_GET['filter'];
+	}
+
+	if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
+	{
+		checkSession('request');
+		validateToken('admin-hook', 'request');
+
+		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']) : '';
+
+			remove_integration_function($_REQUEST['hook'], $function_remove, $file);
+			add_integration_function($_REQUEST['hook'], $function_add, $file);
+
+			redirectexit('action=admin;area=maintain;sa=hooks' . $context['filter_url']);
+		}
+	}
+
+	$list_options = array(
+		'id' => 'list_integration_hooks',
+		'title' => $txt['hooks_title_list'],
+		'items_per_page' => 20,
+		'base_href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
+		'default_sort_col' => 'hook_name',
+		'get_items' => array(
+			'function' => 'get_integration_hooks_data',
+		),
+		'get_count' => array(
+			'function' => 'get_integration_hooks_count',
+		),
+		'no_items_label' => $txt['hooks_no_hooks'],
+		'columns' => array(
+			'hook_name' => array(
+				'header' => array(
+					'value' => $txt['hooks_field_hook_name'],
+				),
+				'data' => array(
+					'db' => 'hook_name',
+				),
+				'sort' =>  array(
+					'default' => 'hook_name',
+					'reverse' => 'hook_name DESC',
+				),
+			),
+			'function_name' => array(
+				'header' => array(
+					'value' => $txt['hooks_field_function_name'],
+				),
+				'data' => array(
+					'function' => create_function('$data', '
+						global $txt;
+
+						if (!empty($data[\'included_file\']))
+							return $txt[\'hooks_field_function\'] . \': \' . $data[\'real_function\'] . \'<br />\' . $txt[\'hooks_field_included_file\'] . \': \' . $data[\'included_file\'];
+						else
+							return $data[\'real_function\'];
+					'),
+				),
+				'sort' =>  array(
+					'default' => 'function_name',
+					'reverse' => 'function_name DESC',
+				),
+			),
+			'file_name' => array(
+				'header' => array(
+					'value' => $txt['hooks_field_file_name'],
+				),
+				'data' => array(
+					'db' => 'file_name',
+				),
+				'sort' =>  array(
+					'default' => 'file_name',
+					'reverse' => 'file_name DESC',
+				),
+			),
+			'status' => array(
+				'header' => array(
+					'value' => $txt['hooks_field_hook_exists'],
+					'style' => 'width:3%;',
+				),
+				'data' => array(
+					'function' => create_function('$data', '
+						global $txt, $settings, $scripturl, $context;
+
+						$change_status = array(\'before\' => \'\', \'after\' => \'\');
+						if ($data[\'can_be_disabled\'] && $data[\'status\'] != \'deny\')
+						{
+							$change_status[\'before\'] = \'<a href="\' . $scripturl . \'?action=admin;area=maintain;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_url\'] . \';\' . $context[\'admin-hook_token_var\'] . \'=\' . $context[\'admin-hook_token\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'" onclick="return confirm(\' . javaScriptEscape($txt[\'quickmod_confirm\']) . \');">\';
+							$change_status[\'after\'] = \'</a>\';
+						}
+						return $change_status[\'before\'] . \'<img src="\' . $settings[\'images_url\'] . \'/admin/post_moderation_\' . $data[\'status\'] . \'.png" alt="\' . $data[\'img_text\'] . \'" title="\' . $data[\'img_text\'] . \'" />\' . $change_status[\'after\'];
+					'),
+					'class' => 'centertext',
+				),
+				'sort' =>  array(
+					'default' => 'status',
+					'reverse' => 'status DESC',
+				),
+			),
+		),
+		'additional_rows' => array(
+			array(
+				'position' => 'after_title',
+				'value' => $txt['hooks_disable_instructions'] . '<br />
+					' . $txt['hooks_disable_legend'] . ':
+									<ul style="list-style: none;">
+					<li><img src="' . $settings['images_url'] . '/admin/post_moderation_allow.png" alt="' . $txt['hooks_active'] . '" title="' . $txt['hooks_active'] . '" /> ' . $txt['hooks_disable_legend_exists'] . '</li>
+					<li><img src="' . $settings['images_url'] . '/admin/post_moderation_moderate.png" alt="' . $txt['hooks_disabled'] . '" title="' . $txt['hooks_disabled'] . '" /> ' . $txt['hooks_disable_legend_disabled'] . '</li>
+					<li><img src="' . $settings['images_url'] . '/admin/post_moderation_deny.png" alt="' . $txt['hooks_missing'] . '" title="' . $txt['hooks_missing'] . '" /> ' . $txt['hooks_disable_legend_missing'] . '</li>
+				</ul>'
+			),
+		),
+	);
+
+	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=maintain;sa=hooks;do=remove;hook=\' . $data[\'hook_name\'] . \';function=\' . urlencode($data[\'function_name\']) . $context[\'filter_url\'] . \';\' . $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=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
+		'name' => 'list_integration_hooks',
+	);
+
+
+	require_once($sourcedir . '/Subs-List.php');
+	createList($list_options);
+
+	$context['page_title'] = $txt['hooks_title_list'];
+	$context['sub_template'] = 'show_list';
+	$context['default_list'] = 'list_integration_hooks';
+}
+
+/**
+ * Gets all of the files in a directory and its chidren directories
+ *
+ * @param type $dir_path
+ * @return array
+ */
+function get_files_recursive($dir_path)
+{
+	$files = array();
+
+	if ($dh = opendir($dir_path))
+	{
+		while (($file = readdir($dh)) !== false)
+		{
+			if ($file != '.' && $file != '..')
+			{
+				if (is_dir($dir_path . '/' . $file))
+					$files = array_merge($files, get_files_recursive($dir_path . '/' . $file));
+				else
+					$files[] = array('dir' => $dir_path, 'name' => $file);
+			}
+		}
+	}
+	closedir($dh);
+
+	return $files;
+}
+
+/**
+ * Callback function for the integration hooks list (list_integration_hooks)
+ * Gets all of the hooks in the system and their status
+ * Would be better documented if Ema was not lazy
+ *
+ * @param type $start
+ * @param type $per_page
+ * @param type $sort
+ * @return array
+ */
+function get_integration_hooks_data($start, $per_page, $sort)
+{
+	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl, $modSettings;
+
+	$hooks = $temp_hooks = get_integration_hooks();
+	$hooks_data = $temp_data = $hook_status = array();
+
+	$files = get_files_recursive($sourcedir);
+	if (!empty($files))
+	{
+		foreach ($files as $file)
+		{
+			if (is_file($file['dir'] . '/' . $file['name']) && substr($file['name'], -4) === '.php')
+			{
+				$fp = fopen($file['dir'] . '/' . $file['name'], 'rb');
+				$fc = fread($fp, filesize($file['dir'] . '/' . $file['name']));
+				fclose($fp);
+
+				foreach ($temp_hooks as $hook => $functions)
+				{
+					foreach ($functions as $function_o)
+					{
+						$hook_name = str_replace(']', '', $function_o);
+						if (strpos($hook_name, '::') !== false)
+						{
+							$function = explode('::', $hook_name);
+							$function = $function[1];
+						}
+						else
+							$function = $hook_name;
+						$function = explode(':', $function);
+						$function = $function[0];
+
+						if (substr($hook, -8) === '_include')
+						{
+							$hook_status[$hook][$function]['exists'] = file_exists(strtr(trim($function), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
+							// I need to know if there is at least one function called in this file.
+							$temp_data['include'][basename($function)] = array('hook' => $hook, 'function' => $function);
+							unset($temp_hooks[$hook][$function_o]);
+						}
+						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($function) . '(') !== false)
+						{
+							$hook_status[$hook][$hook_name]['exists'] = true;
+							$hook_status[$hook][$hook_name]['in_file'] = $file['name'];
+							// I want to remember all the functions called within this file (to check later if they are enabled or disabled and decide if the integrare_*_include of that file can be disabled too)
+							$temp_data['function'][$file['name']][] = $function_o;
+							unset($temp_hooks[$hook][$function_o]);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	$sort_types = array(
+		'hook_name' => array('hook', SORT_ASC),
+		'hook_name DESC' => array('hook', SORT_DESC),
+		'function_name' => array('function', SORT_ASC),
+		'function_name DESC' => array('function', SORT_DESC),
+		'file_name' => array('file_name', SORT_ASC),
+		'file_name DESC' => array('file_name', SORT_DESC),
+		'status' => array('status', SORT_ASC),
+		'status DESC' => array('status', SORT_DESC),
+	);
+
+	$sort_options = $sort_types[$sort];
+	$sort = array();
+	$hooks_filters = array();
+
+	foreach ($hooks as $hook => $functions)
+	{
+		$hooks_filters[] = '<option ' . ($context['current_filter'] == $hook ? 'selected="selected" ' : '') . 'onclick="window.location = \'' . $scripturl . '?action=admin;area=maintain;sa=hooks;filter=' . $hook . '\';">' . $hook . '</option>';
+		foreach ($functions as $function)
+		{
+			$enabled = strstr($function, ']') === false;
+			$function = str_replace(']', '', $function);
+
+			// This is a not an include and the function is included in a certain file (if not it doesn't exists so don't care)
+			if (substr($hook, -8) !== '_include' && isset($hook_status[$hook][$function]['in_file']))
+			{
+				$current_hook = isset($temp_data['include'][$hook_status[$hook][$function]['in_file']]) ? $temp_data['include'][$hook_status[$hook][$function]['in_file']] : '';
+				$enabled = false;
+
+				// Checking all the functions within this particular file
+				// if any of them is enable then the file *must* be included and the integrate_*_include hook cannot be disabled
+				foreach ($temp_data['function'][$hook_status[$hook][$function]['in_file']] as $func)
+					$enabled = $enabled || strstr($func, ']') !== false;
+
+				if (!$enabled &&  !empty($current_hook))
+					$hook_status[$current_hook['hook']][$current_hook['function']]['enabled'] = true;
+			}
+		}
+	}
+
+	if (!empty($hooks_filters))
+		$context['insert_after_template'] .= '
+		<script type="text/javascript"><!-- // --><![CDATA[
+			var hook_name_header = document.getElementById(\'header_list_integration_hooks_hook_name\');
+			hook_name_header.innerHTML += ' . JavaScriptEscape('<select style="margin-left:15px;"><option>---</option><option onclick="window.location = \'' . $scripturl . '?action=admin;area=maintain;sa=hooks\';">' . $txt['hooks_reset_filter'] . '</option>' . implode('', $hooks_filters) . '</select>'). ';
+		// ]]></script>';
+
+	$temp_data = array();
+	$id = 0;
+
+	foreach ($hooks as $hook => $functions)
+	{
+		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
+		{
+			foreach ($functions as $function)
+			{
+				$enabled = strstr($function, ']') === false;
+				$function = str_replace(']', '', $function);
+				$hook_exists = !empty($hook_status[$hook][$function]['exists']);
+				$file_name = isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ((substr($hook, -8) === '_include') ? 'zzzzzzzzz' : 'zzzzzzzza');
+				$sort[] = $$sort_options[0];
+
+				if (strpos($function, '::') !== false)
+				{
+					$function = explode('::', $function);
+					$function = $function[1];
+				}
+				$exploded = explode(':', $function);
+
+				$temp_data[] = array(
+					'id' => 'hookid_' . $id++,
+					'hook_name' => $hook,
+					'function_name' => $function,
+					'real_function' => $exploded[0],
+					'included_file' => isset($exploded[1]) ? strtr(trim($exploded[1]), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])) : '',
+					'file_name' => (isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ''),
+					'hook_exists' => $hook_exists,
+					'status' => $hook_exists ? ($enabled ? 'allow' : 'moderate') : 'deny',
+					'img_text' => $txt['hooks_' . ($hook_exists ? ($enabled ? 'active' : 'disabled') : 'missing')],
+					'enabled' => $enabled,
+					'can_be_disabled' => !isset($hook_status[$hook][$function]['enabled']),
+				);
+			}
+		}
+	}
+
+	array_multisort($sort, $sort_options[1], $temp_data);
+
+	$counter = 0;
+	$start++;
+
+	foreach ($temp_data as $data)
+	{
+		if (++$counter < $start)
+			continue;
+		elseif ($counter == $start + $per_page)
+			break;
+
+		$hooks_data[] = $data;
+	}
+
+	return $hooks_data;
+}
+
+/**
+ * Simply returns the total count of integraion hooks
+ * Used but the intergation hooks list function (list_integration_hooks)
+ *
+ * @global type $context
+ * @return int
+ */
+function get_integration_hooks_count()
+{
+	global $context;
+
+	$hooks = get_integration_hooks();
+	$hooks_count = 0;
+
+	$context['filter'] = false;
+	if (isset($_GET['filter']))
+		$context['filter'] = $_GET['filter'];
+
+	foreach ($hooks as $hook => $functions)
+	{
+		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
+			$hooks_count += count($functions);
+	}
+
+	return $hooks_count;
+}
+
+/**
+ * Parses modSettings to create integration hook array
+ *
+ * @staticvar type $integration_hooks
+ * @return type
+ */
+function get_integration_hooks()
+{
+	global $modSettings;
+	static $integration_hooks;
+
+	if (!isset($integration_hooks))
+	{
+		$integration_hooks = array();
+		foreach ($modSettings as $key => $value)
+		{
+			if (!empty($value) && substr($key, 0, 10) === 'integrate_')
+				$integration_hooks[$key] = explode(',', $value);
+		}
+	}
+
+	return $integration_hooks;
+}
+
 ?>

+ 2 - 2
Sources/ManageMembergroups.php

@@ -507,7 +507,7 @@ function AddMembergroup()
 		));
 
 		// We did it.
-		logAction('add_group', array('group' => $_POST['group_name']), 'admin');
+		logAction('add_group', array('group' => $smcFunc['htmlspecialchars']($_POST['group_name'])), 'admin');
 
 		// Go change some more settings.
 		redirectexit('action=admin;area=membergroups;sa=edit;group=' . $id_group);
@@ -996,7 +996,7 @@ function EditMembergroup()
 		));
 
 		// Log the edit.
-		logAction('edited_group', array('group' => $_POST['group_name']), 'admin');
+		logAction('edited_group', array('group' => $smcFunc['htmlspecialchars']($_POST['group_name'])), 'admin');
 
 		redirectexit('action=admin;area=membergroups');
 	}

+ 26 - 29
Sources/ManageNews.php

@@ -522,7 +522,6 @@ function ComposeMailing()
 		$context['recipients']['emails'] = !empty($_POST['emails']) ? explode(';', $_POST['emails']) : array();
 		$context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
 		$context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
-		$context['max_id_member'] = !empty($_POST['max_id_member']) ? (int) $_POST['max_id_member'] : 0;
 		$context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
 		$context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
 
@@ -659,12 +658,12 @@ function ComposeMailing()
 	// For progress bar!
 	$context['total_emails'] = count($context['recipients']['emails']);
 	$request = $smcFunc['db_query']('', '
-		SELECT MAX(id_member)
+		SELECT COUNT(*)
 		FROM {db_prefix}members',
 		array(
 		)
 	);
-	list ($context['max_id_member']) = $smcFunc['db_fetch_row']($request);
+	list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
 	$smcFunc['db_free_result']($request);
 
 	// Clean up the arrays.
@@ -708,10 +707,23 @@ function SendMailing($clean_only = false)
 	$context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
 	$context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
 	$context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
-	$context['max_id_member'] = !empty($_POST['max_id_member']) ? (int) $_POST['max_id_member'] : 0;
 	$context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
 	$context['parse_html'] = !empty($_POST['parse_html']) ? '1' : '0';
 
+	//One can't simply nullify things around
+	if(empty($_REQUEST['total_members'])) {
+		$request = $smcFunc['db_query']('', '
+			SELECT COUNT(*)
+			FROM {db_prefix}members',
+			array(
+			)
+		);
+		list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
+		$smcFunc['db_free_result']($request);
+	} else {
+		$context['total_members'] = (int) $_REQUEST['total_members'];
+	}
+
 	// Create our main context.
 	$context['recipients'] = array(
 		'groups' => array(),
@@ -775,7 +787,7 @@ function SendMailing($clean_only = false)
 		foreach ($addressed as $curmem)
 		{
 			$curmem = trim($curmem);
-			if ($curmem != '')
+			if ($curmem != '' && preg_match('~^[0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $curmem) !== 0)
 				$context['recipients']['emails'][$curmem] = $curmem;
 		}
 	}
@@ -884,8 +896,6 @@ function SendMailing($clean_only = false)
 		$i++;
 	}
 
-	// Got some more to send this batch?
-	$last_id_member = 0;
 	if ($i < $num_at_once)
 	{
 		// Need to build quite a query!
@@ -937,16 +947,13 @@ function SendMailing($clean_only = false)
 		$result = $smcFunc['db_query']('', '
 			SELECT mem.id_member, mem.email_address, mem.real_name, mem.id_group, mem.additional_groups, mem.id_post_group
 			FROM {db_prefix}members AS mem
-			WHERE mem.id_member > {int:min_id_member}
-				AND mem.id_member < {int:max_id_member}
-				AND ' . $sendQuery . '
+			WHERE ' . $sendQuery . '
 				AND mem.is_activated = {int:is_activated}
 			ORDER BY mem.id_member ASC
-			LIMIT {int:atonce}',
+			LIMIT {int:start}, {int:atonce}',
 			array_merge($sendParams, array(
-				'min_id_member' => $context['start'],
-				'max_id_member' => $context['start'] + $num_at_once - $i,
-				'atonce' => $num_at_once - $i,
+				'start' => $context['start'],
+				'atonce' => $num_at_once,
 				'regular_group' => 0,
 				'notify_announcements' => 1,
 				'is_activated' => 1,
@@ -955,8 +962,6 @@ function SendMailing($clean_only = false)
 
 		while ($row = $smcFunc['db_fetch_assoc']($result))
 		{
-			$last_id_member = $row['id_member'];
-
 			// What groups are we looking at here?
 			if (empty($row['additional_groups']))
 				$groups = array($row['id_group'], $row['id_post_group']);
@@ -999,26 +1004,18 @@ function SendMailing($clean_only = false)
 		$smcFunc['db_free_result']($result);
 	}
 
-	// If used our batch assume we still have a member.
-	if ($i >= $num_at_once)
-		$last_id_member = $context['start'];
-	// Or we didn't have one in range?
-	elseif (empty($last_id_member) && $context['start'] + $num_at_once < $context['max_id_member'])
-		$last_id_member = $context['start'] + $num_at_once;
-	// If we have no id_member then we're done.
-	elseif (empty($last_id_member) && empty($context['recipients']['emails']))
+
+	$context['start'] = $context['start'] + $num_at_once;
+	if (empty($context['recipients']['emails']) && ($context['start'] >= $context['total_members']))
 	{
 		// Log this into the admin log.
 		logAction('newsletter', array(), 'admin');
-
 		redirectexit('action=admin');
 	}
 
-	$context['start'] = $last_id_member;
-
 	// Working out progress is a black art of sorts.
-	$percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['max_id_member'])));
-	$percentMembers = ($context['start'] / $context['max_id_member']) * ($context['max_id_member'] / ($context['total_emails'] + $context['max_id_member']));
+	$percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['total_members'])));
+	$percentMembers = ($context['start'] / $context['total_members']) * ($context['total_members'] / ($context['total_emails'] + $context['total_members']));
 	$context['percentage_done'] = round(($percentEmails + $percentMembers) * 100, 2);
 
 	$context['page_title'] = $txt['admin_newsletters'];

+ 13 - 0
Sources/ManagePaid.php

@@ -166,6 +166,19 @@ function ModifySubscriptionSettings($return_config = false)
 	{
 		checkSession();
 
+		// Check the email addresses were actually email addresses.
+		if (!empty($_POST['paid_email_to']))
+		{
+			$email_addresses = array();
+			foreach (explode(',', $_POST['paid_email_to']) as $email)
+			{
+				$email = trim($email);
+				if (!empty($email) && preg_match('~^[0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $email))
+					$email_addresses[] = $email;
+				$_POST['paid_email_to'] = implode(',', $email_addresses);
+			}
+		}
+
 		// Sort out the currency stuff.
 		if ($_POST['paid_currency'] != 'other')
 		{

+ 1 - 1
Sources/ManagePermissions.php

@@ -1144,7 +1144,7 @@ function setPermissionLevel($level, $group, $profile = 'null')
 	// Moderator - ie. moderators :P.  They can do what standard can, and more.
 	$groupLevels['global']['moderator'] = array_merge($groupLevels['global']['standard'], array(
 		'calendar_post',
-		'calen	ddar_edit_own',
+		'calendar_edit_own',
 		'access_mod_center',
 		'issue_warning',
 	));

+ 11 - 0
Sources/ManageScheduledTasks.php

@@ -157,9 +157,20 @@ function ScheduledTasks()
 			}
 		}
 		$smcFunc['db_free_result']($request);
+
+		// If we had any errors, push them to session so we can pick them up next time to tell the user.
+		if (!empty($context['scheduled_errors']))
+			$_SESSION['st_error'] = $context['scheduled_errors'];
+
 		redirectexit('action=admin;area=scheduledtasks;done');
 	}
 
+	if (isset($_SESSION['st_error']))
+	{
+		$context['scheduled_errors'] = $_SESSION['st_error'];
+		unset ($_SESSION['st_error']);
+	}
+
 	$listOptions = array(
 		'id' => 'scheduled_tasks',
 		'title' => $txt['maintain_tasks'],

+ 82 - 5
Sources/ManageServer.php

@@ -94,6 +94,7 @@ function ModifySettings()
 		'general' => 'ModifyGeneralSettings',
 		'database' => 'ModifyDatabaseSettings',
 		'cookie' => 'ModifyCookieSettings',
+		'security' => 'ModifyGeneralSecuritySettings',
 		'cache' => 'ModifyCacheSettings',
 		'loads' => 'ModifyLoadBalancingSettings',
 		'phpinfo' => 'ShowPHPinfoSettings',
@@ -262,11 +263,11 @@ function ModifyCookieSettings($return_config = false)
 		// Cookies...
 		array('cookiename', $txt['cookie_name'], 'file', 'text', 20),
 		array('cookieTime', $txt['cookieTime'], 'db', 'int', 'postinput' => $txt['minutes']),
-		array('localCookies', $txt['localCookies'], 'subtext' => $txt['localCookies_note'], 'db', 'check', false, 'localCookies'),
-		array('globalCookies', $txt['globalCookies'], 'subtext' => $txt['globalCookies_note'], 'db', 'check', false, 'globalCookies'),
-		array('globalCookiesDomain', $txt['globalCookiesDomain'], 'subtext' => $txt['globalCookiesDomain_note'], 'db', 'text', false, 'globalCookiesDomain'),
-		array('secureCookies', $txt['secureCookies'], 'subtext' => $txt['secureCookies_note'], 'db', 'check', false, 'secureCookies',  'disabled' => !isset($_SERVER['HTTPS']) || !(strtolower($_SERVER['HTTPS']) == 'on' || strtolower($_SERVER['HTTPS']) == '1')),
-		array('httponlyCookies', $txt['httponlyCookies'], 'subtext' => $txt['httponlyCookies_note'], 'db', 'check', false, 'httponlyCookies'),
+		array('localCookies', $txt['localCookies'], 'db', 'check', false, 'localCookies'),
+		array('globalCookies', $txt['globalCookies'], 'db', 'check', false, 'globalCookies'),
+		array('globalCookiesDomain', $txt['globalCookiesDomain'], 'db', 'text', false, 'globalCookiesDomain'),
+		array('secureCookies', $txt['secureCookies'], 'db', 'check', false, 'secureCookies',  'disabled' => !isset($_SERVER['HTTPS']) || !(strtolower($_SERVER['HTTPS']) == 'on' || strtolower($_SERVER['HTTPS']) == '1')),
+		array('httponlyCookies', $txt['httponlyCookies'], 'db', 'check', false, 'httponlyCookies'),
 		'',
 		// Sessions
 		array('databaseSession_enable', $txt['databaseSession_enable'], 'db', 'check', false, 'databaseSession_enable'),
@@ -274,6 +275,23 @@ function ModifyCookieSettings($return_config = false)
 		array('databaseSession_lifetime', $txt['databaseSession_lifetime'], 'db', 'int', false, 'databaseSession_lifetime', 'postinput' => $txt['seconds']),
 	);
 
+	addInlineJavascript('
+	function hideGlobalCookies()
+	{
+		var usingLocal = $("#localCookies").prop("checked");
+		$("#setting_globalCookies").closest("dt").toggle(!usingLocal);
+		$("#globalCookies").closest("dd").toggle(!usingLocal);
+
+		var usingGlobal = !usingLocal && $("#globalCookies").prop("checked");
+		$("#setting_globalCookiesDomain").closest("dt").toggle(usingGlobal);
+		$("#globalCookiesDomain").closest("dd").toggle(usingGlobal);
+	};
+	hideGlobalCookies();
+
+	$("#localCookies, #globalCookies").click(function() {
+		hideGlobalCookies();
+	});', true);
+
 	call_integration_hook('integrate_cookie_settings', array(&$config_vars));
 
 	if ($return_config)
@@ -287,6 +305,10 @@ function ModifyCookieSettings($return_config = false)
 	{
 		call_integration_hook('integrate_save_cookie_settings');
 
+		// Local and global do not play nicely together.
+		if (!empty($_POST['localCookies']) && empty($_POST['globalCookies']))
+			unset ($_POST['globalCookies']);
+
 		if (!empty($_POST['globalCookiesDomain']) && strpos($boardurl, $_POST['globalCookiesDomain']) === false)
 			fatal_lang_error('invalid_cookie_domain', false);
 
@@ -315,6 +337,61 @@ function ModifyCookieSettings($return_config = false)
 	prepareServerSettingsContext($config_vars);
 }
 
+/**
+ * Settings really associated with general security aspects.
+ *
+ * @param $return_config
+ */
+function ModifyGeneralSecuritySettings($return_config = false)
+{
+	global $txt, $scripturl, $context, $settings, $sc, $modSettings;
+
+	$config_vars = array(
+			array('check', 'guest_hideContacts'),
+			array('check', 'make_email_viewable'),
+		'',
+			array('int', 'failed_login_threshold'),
+			array('int', 'loginHistoryDays'),
+		'',
+			array('check', 'securityDisable'),
+			array('check', 'securityDisable_moderate'),
+		'',
+			// Reactive on email, and approve on delete
+			array('check', 'send_validation_onChange'),
+			array('check', 'approveAccountDeletion'),
+		'',
+			// Password strength.
+			array('select', 'password_strength', array($txt['setting_password_strength_low'], $txt['setting_password_strength_medium'], $txt['setting_password_strength_high'])),
+			array('check', 'enable_password_conversion'),
+		'',
+			// Reporting of personal messages?
+			array('check', 'enableReportPM'),
+		'',
+			array('select', 'frame_security', array('SAMEORIGIN' => $txt['setting_frame_security_SAMEORIGIN'], 'DENY' => $txt['setting_frame_security_DENY'], 'DISABLE' => $txt['setting_frame_security_DISABLE'])),
+	);
+
+	call_integration_hook('integrate_general_security_settings', array(&$config_vars));
+
+	if ($return_config)
+		return $config_vars;
+
+	// Saving?
+	if (isset($_GET['save']))
+	{
+		saveDBSettings($config_vars);
+
+		call_integration_hook('integrate_save_general_security_settings');
+
+		writeLog();
+		redirectexit('action=admin;area=serversettings;sa=security;' . $context['session_var'] . '=' . $context['session_id']);
+	}
+
+	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;save;sa=security';
+	$context['settings_title'] = $txt['security_settings'];
+
+	prepareDBSettingContext($config_vars);
+}
+
 /**
  * Simply modifying cache functions
  *

+ 180 - 638
Sources/ManageSettings.php

@@ -63,11 +63,7 @@ function ModifyFeatureSettings()
 
 	call_integration_hook('integrate_modify_features', array(&$subActions));
 
-	// If Advanced Profile Fields are disabled don't show the setting page
-	if (!in_array('cp', $context['admin_features']))
-		unset($subActions['profile']);
-
-	// Same for Karma
+	// If karma is disabled don't show the setting page.
 	if (!in_array('k', $context['admin_features']))
 		unset($subActions['karma']);
 
@@ -108,7 +104,6 @@ function ModifySecuritySettings()
 	$context['page_title'] = $txt['admin_security_moderation'];
 
 	$subActions = array(
-		'general' => 'ModifyGeneralSecuritySettings',
 		'spam' => 'ModifySpamSettings',
 		'moderation' => 'ModifyModerationSettings',
 	);
@@ -119,7 +114,7 @@ function ModifySecuritySettings()
 	if (!in_array('w', $context['admin_features']))
 		unset($subActions['moderation']);
 
-	loadGeneralSettingParameters($subActions, 'general');
+	loadGeneralSettingParameters($subActions, 'spam');
 
 	// Load up all the tabs...
 	$context[$context['admin_menu_name']]['tab_data'] = array(
@@ -127,8 +122,6 @@ function ModifySecuritySettings()
 		'help' => 'securitysettings',
 		'description' => $txt['security_settings_desc'],
 		'tabs' => array(
-			'general' => array(
-			),
 			'spam' => array(
 				'description' => $txt['antispam_Settings_desc'] ,
 			),
@@ -152,7 +145,6 @@ function ModifyModSettings()
 
 	$subActions = array(
 		'general' => 'ModifyGeneralModSettings',
-		'hooks' => 'list_integration_hooks',
 		// Mod authors, once again, if you have a whole section to add do it AFTER this line, and keep a comma at the end.
 	);
 
@@ -169,8 +161,6 @@ function ModifyModSettings()
 		'tabs' => array(
 			'general' => array(
 			),
-			'hooks' => array(
-			),
 		),
 	);
 
@@ -202,61 +192,6 @@ function ModifyCoreFeatures($return_config = false)
 				'cal_enabled' => 1,
 			),
 		),
-		// cp = custom profile fields.
-		'cp' => array(
-			'url' => 'action=admin;area=featuresettings;sa=profile',
-			'save_callback' => create_function('$value', '
-				global $smcFunc;
-				if (!$value)
-				{
-					$smcFunc[\'db_query\'](\'\', \'
-						UPDATE {db_prefix}custom_fields
-						SET active = 0\');
-				}
-			'),
-			'setting_callback' => create_function('$value', '
-				if (!$value)
-					return array(
-						\'disabled_profile_fields\' => \'\',
-						\'registration_fields\' => \'\',
-						\'displayFields\' => \'\',
-					);
-				else
-					return array();
-			'),
-		),
-		// dr = drafts
-		'dr' => array(
-			'url' => 'action=admin;area=managedrafts',
-			'settings' => array(
-				'drafts_enabled' => 1,
-				'drafts_post_enabled' => 2,
-				'drafts_pm_enabled' => 2,
-				'drafts_autosave_enabled' => 2,
-				'drafts_show_saved_enabled' => 2,
-			),
-			'setting_callback' => create_function('$value', '
-				global $smcFunc, $sourcedir;
-
-				// Set the correct disabled value for the scheduled task.
-				$smcFunc[\'db_query\'](\'\', \'
-					UPDATE {db_prefix}scheduled_tasks
-					SET disabled = {int:disabled}
-					WHERE task = {string:task}\',
-					array(
-						\'disabled\' => $value ? 0 : 1,
-						\'task\' => \'remove_old_drafts\',
-					)
-				);
-			'),
-		),
-		// ih = Integration Hooks Handling.
-		'ih' => array(
-			'url' => 'action=admin;area=modsettings;sa=hooks',
-			'settings' => array(
-				'handlinghooks_enabled' => 1,
-			),
-		),
 		// k = karma.
 		'k' => array(
 			'url' => 'action=admin;area=featuresettings;sa=karma',
@@ -264,13 +199,6 @@ function ModifyCoreFeatures($return_config = false)
 				'karmaMode' => 2,
 			),
 		),
-		// ml = moderation log.
-		'ml' => array(
-			'url' => 'action=admin;area=logs;sa=modlog',
-			'settings' => array(
-				'modlog_enabled' => 1,
-			),
-		),
 		// pm = post moderation.
 		'pm' => array(
 			'url' => 'action=admin;area=permissions;sa=postmod',
@@ -317,10 +245,6 @@ function ModifyCoreFeatures($return_config = false)
 				}
 			'),
 		),
-		// rg = report generator.
-		'rg' => array(
-			'url' => 'action=admin;area=reports',
-		),
 		// w = warning.
 		'w' => array(
 			'url' => 'action=admin;area=securitysettings;sa=moderation',
@@ -580,66 +504,6 @@ function ModifyBasicSettings($return_config = false)
 	prepareDBSettingContext($config_vars);
 }
 
-/**
- * Settings really associated with general security aspects.
- *
- * @param $return_config
- */
-function ModifyGeneralSecuritySettings($return_config = false)
-{
-	global $txt, $scripturl, $context, $settings, $sc, $modSettings;
-
-	$config_vars = array(
-			array('check', 'guest_hideContacts'),
-			array('check', 'make_email_viewable'),
-		'',
-			array('int', 'failed_login_threshold'),
-			array('int', 'loginHistoryDays'),
-		'',
-			array('check', 'enableErrorLogging'),
-			array('check', 'enableErrorQueryLogging'),
-		'',
-			array('check', 'securityDisable'),
-			array('check', 'securityDisable_moderate'),
-		'',
-			// Reactive on email, and approve on delete
-			array('check', 'send_validation_onChange'),
-			array('check', 'approveAccountDeletion'),
-		'',
-			// Password strength.
-			array('select', 'password_strength', array($txt['setting_password_strength_low'], $txt['setting_password_strength_medium'], $txt['setting_password_strength_high'])),
-			array('check', 'enable_password_conversion'),
-		'',
-			// Reporting of personal messages?
-			array('check', 'enableReportPM'),
-		'',
-			array('select', 'frame_security', array('SAMEORIGIN' => $txt['setting_frame_security_SAMEORIGIN'], 'DENY' => $txt['setting_frame_security_DENY'], 'DISABLE' => $txt['setting_frame_security_DISABLE'])),
-	);
-
-	call_integration_hook('integrate_general_security_settings', array(&$config_vars));
-
-	if ($return_config)
-		return $config_vars;
-
-	// Saving?
-	if (isset($_GET['save']))
-	{
-		checkSession();
-
-		saveDBSettings($config_vars);
-
-		call_integration_hook('integrate_save_general_security_settings');
-
-		writeLog();
-		redirectexit('action=admin;area=securitysettings;sa=general');
-	}
-
-	$context['post_url'] = $scripturl . '?action=admin;area=securitysettings;save;sa=general';
-	$context['settings_title'] = $txt['mods_cat_security_general'];
-
-	prepareDBSettingContext($config_vars);
-}
-
 /**
  * Allows modifying the global layout settings in the forum
  * Accessed through ?action=admin;area=featuresettings;sa=layout;
@@ -815,7 +679,7 @@ function ModifyModerationSettings($return_config = false)
  */
 function ModifySpamSettings($return_config = false)
 {
-	global $txt, $scripturl, $context, $settings, $sc, $modSettings, $smcFunc;
+	global $txt, $scripturl, $context, $settings, $sc, $modSettings, $smcFunc, $language;
 
 	// Generate a sample registration image.
 	$context['use_graphic_library'] = in_array('gd', get_loaded_extensions());
@@ -848,25 +712,58 @@ function ModifySpamSettings($return_config = false)
 	if ($return_config)
 		return $config_vars;
 
-	// Load any question and answers!
+	// Firstly, figure out what languages we're dealing with, and do a little processing for the form's benefit.
+	getLanguages();
+	$context['qa_languages'] = array();
+	foreach ($context['languages'] as $lang_id => $lang)
+	{
+		$lang_id = strtr($lang_id, array('-utf8' => ''));
+		$lang['name'] = strtr($lang['name'], array('-utf8' => ''));
+		$context['qa_languages'][$lang_id] = $lang;
+	}
+
+	// Secondly, load any questions we currently have.
 	$context['question_answers'] = array();
 	$request = $smcFunc['db_query']('', '
-		SELECT id_comment, body AS question, recipient_name AS answer
-		FROM {db_prefix}log_comments
-		WHERE comment_type = {string:ver_test}',
-		array(
-			'ver_test' => 'ver_test',
-		)
+		SELECT id_question, lngfile, question, answers
+		FROM {db_prefix}qanda'
 	);
 	while ($row = $smcFunc['db_fetch_assoc']($request))
 	{
-		$context['question_answers'][$row['id_comment']] = array(
-			'id' => $row['id_comment'],
+		$lang = strtr($row['lngfile'], array('-utf8' => ''));
+		$context['question_answers'][$row['id_question']] = array(
+			'lngfile' => $lang,
 			'question' => $row['question'],
-			'answer' => $row['answer'],
+			'answers' => unserialize($row['answers']),
 		);
+		$context['qa_by_lang'][$lang][] = $row['id_question'];
 	}
-	$smcFunc['db_free_result']($request);
+
+	// Thirdly, push some JavaScript for the form to make it work.
+	addInlineJavascript('
+	var nextrow = ' . (!empty($context['question_answers']) ? max(array_keys($context['question_answers'])) + 1 : 1) . ';
+	$(".qa_link a").click(function() {
+		var id = $(this).parent().attr("id").substring(6);
+		$("#qa_fs_" + id).show();
+		$(this).parent().hide();
+	});
+	$(".qa_fieldset legend a").click(function() {
+		var id = $(this).closest("fieldset").attr("id").substring(6);
+		$("#qa_dt_" + id).show();
+		$(this).closest("fieldset").hide();
+	});
+	$(".qa_add_question a").click(function() {
+		var id = $(this).closest("fieldset").attr("id").substring(6);
+		$(\'<dt><input type="text" name="question[\' + id + \'][\' + nextrow + \']" value="" size="50" class="input_text verification_question" /></dt><dd><input type="text" name="answer[\' + id + \'][\' + nextrow + \'][]" value="" size="50" class="input_text verification_answer" / ><div class="qa_add_answer"><a href="javascript:void(0);" onclick="return addAnswer(this);">[ \' + ' . JavaScriptEscape($txt['setup_verification_add_answer']) . ' + \' ]</a></div></dd>\').insertBefore($(this).parent());
+		nextrow++;
+	});
+	function addAnswer(obj)
+	{
+		var attr = $(obj).closest("dd").find(".verification_answer:last").attr("name");
+		$(\'<input type="text" name="\' + attr + \'" value="" size="50" class="input_text verification_answer" />\').insertBefore($(obj).closest("div"));
+		return false;
+	}
+	$("#qa_dt_' . $language . ' a").click();', true);
 
 	// Saving?
 	if (isset($_GET['save']))
@@ -886,71 +783,124 @@ function ModifySpamSettings($return_config = false)
 		$save_vars[] = array('text', 'pm_spam_settings');
 
 		// Handle verification questions.
-		$questionInserts = array();
-		$count_questions = 0;
-		foreach ($_POST['question'] as $id => $question)
+		$changes = array(
+			'insert' => array(),
+			'replace' => array(),
+			'delete' => array(),
+		);
+		$qs_per_lang = array();
+		foreach ($context['qa_languages'] as $lang_id => $dummy)
 		{
-			$question = trim($smcFunc['htmlspecialchars']($question, ENT_COMPAT, $context['character_set']));
-			$answer = trim($smcFunc['strtolower']($smcFunc['htmlspecialchars']($_POST['answer'][$id], ENT_COMPAT, $context['character_set'])));
-
-			// Already existed?
-			if (isset($context['question_answers'][$id]))
+			// If we had some questions for this language before, but don't now, delete everything from that language.
+			if ((!isset($_POST['question'][$lang_id]) || !is_array($_POST['question'][$lang_id])) && !empty($context['qa_by_lang'][$lang_id]))
+				$changes['delete'] = array_merge($questions['delete'], $context['qa_by_lang'][$lang_id]);
+
+			// Now step through and see if any existing questions no longer exist.
+			if (!empty($context['qa_by_lang'][$lang_id]))
+				foreach ($context['qa_by_lang'][$lang_id] as $q_id)
+					if (empty($_POST['question'][$lang_id][$q_id]))
+						$changes['delete'][] = $q_id;
+
+			// Now let's see if there are new questions or ones that need updating.
+			foreach ($_POST['question'][$lang_id] as $q_id => $question)
 			{
-				$count_questions++;
-				// Changed?
-				if ($context['question_answers'][$id]['question'] != $question || $context['question_answers'][$id]['answer'] != $answer)
+				// Ignore junky ids.
+				$q_id = (int) $q_id;
+				if ($q_id <= 0)
+					continue;
+
+				// Check the question isn't empty (because they want to delete it?)
+				if (empty($question) || trim($question) == '')
 				{
-					if ($question == '' || $answer == '')
-					{
-						$smcFunc['db_query']('', '
-							DELETE FROM {db_prefix}log_comments
-							WHERE comment_type = {string:ver_test}
-								AND id_comment = {int:id}',
-							array(
-								'id' => $id,
-								'ver_test' => 'ver_test',
-							)
-						);
-						$count_questions--;
-					}
-					else
-						$request = $smcFunc['db_query']('', '
-							UPDATE {db_prefix}log_comments
-							SET body = {string:question}, recipient_name = {string:answer}
-							WHERE comment_type = {string:ver_test}
-								AND id_comment = {int:id}',
-							array(
-								'id' => $id,
-								'ver_test' => 'ver_test',
-								'question' => $question,
-								'answer' => $answer,
-							)
-						);
+					if (isset($context['question_answers'][$q_id]))
+						$changes['delete'][] = $q_id;
+					continue;
+				}
+				$question = $smcFunc['htmlspecialchars'](trim($question));
+
+				// Get the answers. Firstly check there actually might be some.
+				if (!isset($_POST['answer'][$lang_id][$q_id]) || !is_array($_POST['answer'][$lang_id][$q_id]))
+				{
+					if (isset($context['question_answers'][$q_id]))
+						$changes['delete'][] = $q_id;
+					continue;
 				}
+				// Now get them and check that they might be viable.
+				$answers = array();
+				foreach ($_POST['answer'][$lang_id][$q_id] as $answer)
+					if (!empty($answer) && trim($answer) !== '')
+						$answers[] = $smcFunc['htmlspecialchars'](trim($answer));
+				if (empty($answers))
+				{
+					if (isset($context['question_answers'][$q_id]))
+						$changes['delete'][] = $q_id;
+					continue;
+				}
+				$answers = serialize($answers);
+
+				// At this point we know we have a question and some answers. What are we doing with it?
+				if (!isset($context['question_answers'][$q_id]))
+				{
+					// New question. Now, we don't want to randomly consume ids, so we'll set those, rather than trusting the browser's supplied ids.
+					$changes['insert'][] = array($lang_id, $question, $answers);
+				}
+				else
+				{
+					// It's an existing question. Let's see what's changed, if anything.
+					if ($lang_id != $context['question_answers'][$q_id]['lngfile'] || $question != $context['question_answers'][$q_id]['question'] || $answers != $context['question_answers'][$q_id]['answers'])
+						$changes['replace'][$q_id] = array('lngfile' => $lang_id, 'question' => $question, 'answers' => $answers);
+				}
+
+				if (!isset($qs_per_lang[$lang_id]))
+					$qs_per_lang[$lang_id] = 0;
+				$qs_per_lang[$lang_id]++;
 			}
-			// It's so shiney and new!
-			elseif ($question != '' && $answer != '')
+		}
+
+		// OK, so changes?
+		if (!empty($changes['delete']))
+		{
+			$smcFunc['db_query']('', '
+				DELETE FROM {db_prefix}qanda
+				WHERE id_question IN ({array_int:questions})',
+				array(
+					'questions' => $changes['delete'],
+				)
+			);
+		}
+
+		if (!empty($changes['replace']))
+		{
+			foreach ($changes['replace'] as $q_id => $question)
 			{
-				$questionInserts[] = array(
-					'comment_type' => 'ver_test',
-					'body' => $question,
-					'recipient_name' => $answer,
+				$smcFunc['db_query']('', '
+					UPDATE {db_prefix}qanda
+					SET lngfile = {string:lngfile},
+						question = {string:question},
+						answers = {string:answers}
+					WHERE id_question = {int:id_question}',
+					array(
+						'id_question' => $q_id,
+						'lngfile' => $question['lngfile'],
+						'question' => $question['question'],
+						'answers' => $question['answers'],
+					)
 				);
 			}
 		}
 
-		// Any questions to insert?
-		if (!empty($questionInserts))
+		if (!empty($changes['insert']))
 		{
-			$smcFunc['db_insert']('',
-				'{db_prefix}log_comments',
-				array('comment_type' => 'string', 'body' => 'string-65535', 'recipient_name' => 'string-80'),
-				$questionInserts,
-				array('id_comment')
+			$smcFunc['db_insert']('insert',
+				'{db_prefix}qanda',
+				array('lngfile' => 'string-50', 'question' => 'string-255', 'answers' => 'string-65534'),
+				$changes['insert'],
+				array('id_question')
 			);
-			$count_questions++;
 		}
 
+		// Lastly, the count of messages needs to be no more than the lowest number of questions for any one language.
+		$count_questions = empty($qs_per_lang) ? 0 : min($qs_per_lang);
 		if (empty($count_questions) || $_POST['qa_verification_number'] > $count_questions)
 			$_POST['qa_verification_number'] = $count_questions;
 
@@ -959,7 +909,7 @@ function ModifySpamSettings($return_config = false)
 		// Now save.
 		saveDBSettings($save_vars);
 
-		cache_put_data('verificationQuestionIds', null, 300);
+		cache_put_data('verificationQuestions', null, 300);
 
 		redirectexit('action=admin;area=securitysettings;sa=spam');
 	}
@@ -2087,17 +2037,22 @@ function EditCustomProfiles()
  * Allow to edit the settings on the pruning screen.
  * @param $return_config
  */
-function ModifyPruningSettings($return_config = false)
+function ModifyLogSettings($return_config = false)
 {
 	global $txt, $scripturl, $sourcedir, $context, $settings, $sc, $modSettings;
 
 	// Make sure we understand what's going on.
 	loadLanguage('ManageSettings');
 
-	$context['page_title'] = $txt['pruning_title'];
+	$context['page_title'] = $txt['log_settings'];
 
 	$config_vars = array(
+			array('check', 'enableErrorLogging'),
+			array('check', 'enableErrorQueryLogging'),
+			array('check', 'log_ban_hits'),
 			// Even do the pruning?
+			array('title', 'pruning_title'),
+			array('desc', 'pruning_desc'),
 			// The array indexes are there so we can remove/change them before saving.
 			'pruningOptions' => array('check', 'pruningOptions'),
 		'',
@@ -2112,11 +2067,28 @@ function ModifyPruningSettings($return_config = false)
 			// Mod Developers: Do NOT use the pruningOptions master variable for this as SMF Core may overwrite your setting in the future!
 	);
 
-	call_integration_hook('integrate_prune_settings', array(&$config_vars));
+	// We want to be toggling some of these for a nice user experience. If you want to add yours to the list of those magically hidden when the 'pruning' option is off, add to this.
+	$prune_toggle = array('pruneErrorLog', 'pruneModLog', 'pruneBanLog', 'pruneReportLog', 'pruneScheduledTaskLog', 'pruneSpiderHitLog');
+
+	call_integration_hook('integrate_prune_settings', array(&$config_vars, &$prune_toggle));
+
+	$prune_toggle_dt = array();
+	foreach ($prune_toggle as $item)
+		$prune_toggle_dt[] = 'setting_' . $item;
 
 	if ($return_config)
 		return $config_vars;
 
+	addInlineJavascript('
+	function togglePruned()
+	{
+		var newval = $("#pruningOptions").prop("checked");
+		$("#' . implode(', #', $prune_toggle) . '").closest("dd").toggle(newval);
+		$("#' . implode(', #', $prune_toggle_dt) . '").closest("dt").toggle(newval);
+	};
+	togglePruned();
+	$("#pruningOptions").click(function() { togglePruned(); });', true);
+
 	// We'll need this in a bit.
 	require_once($sourcedir . '/ManageServer.php');
 
@@ -2145,11 +2117,11 @@ function ModifyPruningSettings($return_config = false)
 			$_POST['pruningOptions'] = '';
 
 		saveDBSettings($savevar);
-		redirectexit('action=admin;area=logs;sa=pruning');
+		redirectexit('action=admin;area=logs;sa=settings');
 	}
 
-	$context['post_url'] = $scripturl . '?action=admin;area=logs;save;sa=pruning';
-	$context['settings_title'] = $txt['pruning_title'];
+	$context['post_url'] = $scripturl . '?action=admin;area=logs;save;sa=settings';
+	$context['settings_title'] = $txt['log_settings'];
 	$context['sub_template'] = 'show_settings';
 
 	// Get the actual values
@@ -2212,434 +2184,4 @@ function ModifyGeneralModSettings($return_config = false)
 	prepareDBSettingContext($config_vars);
 }
 
-/**
- * Generates a list of integration hooks for display
- * Accessed through ?action=admin;area=modsettings;sa=hooks;
- * Allows for removal or disabing of selected hooks
- */
-function list_integration_hooks()
-{
-	global $sourcedir, $scripturl, $context, $txt, $modSettings, $settings;
-
-	$context['filter_url'] = '';
-	$context['current_filter'] = '';
-	$currentHooks = get_integration_hooks();
-	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($currentHooks)))
-	{
-		$context['filter_url'] = ';filter=' . $_GET['filter'];
-		$context['current_filter'] = $_GET['filter'];
-	}
-
-	if (!empty($modSettings['handlinghooks_enabled']))
-	{
-		if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
-		{
-			checkSession('request');
-			validateToken('admin-hook', 'request');
-
-			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']) : '';
-
-				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_url']);
-			}
-		}
-	}
-
-	$list_options = array(
-		'id' => 'list_integration_hooks',
-		'title' => $txt['hooks_title_list'],
-		'items_per_page' => 20,
-		'base_href' => $scripturl . '?action=admin;area=modsettings;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
-		'default_sort_col' => 'hook_name',
-		'get_items' => array(
-			'function' => 'get_integration_hooks_data',
-		),
-		'get_count' => array(
-			'function' => 'get_integration_hooks_count',
-		),
-		'no_items_label' => $txt['hooks_no_hooks'],
-		'columns' => array(
-			'hook_name' => array(
-				'header' => array(
-					'value' => $txt['hooks_field_hook_name'],
-				),
-				'data' => array(
-					'db' => 'hook_name',
-				),
-				'sort' =>  array(
-					'default' => 'hook_name',
-					'reverse' => 'hook_name DESC',
-				),
-			),
-			'function_name' => array(
-				'header' => array(
-					'value' => $txt['hooks_field_function_name'],
-				),
-				'data' => array(
-					'function' => create_function('$data', '
-						global $txt;
-
-						if (!empty($data[\'included_file\']))
-							return $txt[\'hooks_field_function\'] . \': \' . $data[\'real_function\'] . \'<br />\' . $txt[\'hooks_field_included_file\'] . \': \' . $data[\'included_file\'];
-						else
-							return $data[\'real_function\'];
-					'),
-				),
-				'sort' =>  array(
-					'default' => 'function_name',
-					'reverse' => 'function_name DESC',
-				),
-			),
-			'file_name' => array(
-				'header' => array(
-					'value' => $txt['hooks_field_file_name'],
-				),
-				'data' => array(
-					'db' => 'file_name',
-				),
-				'sort' =>  array(
-					'default' => 'file_name',
-					'reverse' => 'file_name DESC',
-				),
-			),
-			'status' => array(
-				'header' => array(
-					'value' => $txt['hooks_field_hook_exists'],
-					'style' => 'width:3%;',
-				),
-				'data' => array(
-					'function' => create_function('$data', '
-						global $txt, $settings, $scripturl, $context;
-
-						$change_status = array(\'before\' => \'\', \'after\' => \'\');
-						if ($data[\'can_be_disabled\'] && $data[\'status\'] != \'deny\')
-						{
-							$change_status[\'before\'] = \'<a href="\' . $scripturl . \'?action=admin;area=modsettings;sa=hooks;do=\' . ($data[\'enabled\'] ? \'disable\' : \'enable\') . \';hook=\' . $data[\'hook_name\'] . \';function=\' . $data[\'real_function\'] . (!empty($data[\'included_file\']) ? \';includedfile=\' . urlencode($data[\'included_file\']) : \'\') . $context[\'filter_url\'] . \';\' . $context[\'admin-hook_token_var\'] . \'=\' . $context[\'admin-hook_token\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'" onclick="return confirm(\' . javaScriptEscape($txt[\'quickmod_confirm\']) . \');">\';
-							$change_status[\'after\'] = \'</a>\';
-						}
-						return $change_status[\'before\'] . \'<img src="\' . $settings[\'images_url\'] . \'/admin/post_moderation_\' . $data[\'status\'] . \'.png" alt="\' . $data[\'img_text\'] . \'" title="\' . $data[\'img_text\'] . \'" />\' . $change_status[\'after\'];
-					'),
-					'class' => 'centertext',
-				),
-				'sort' =>  array(
-					'default' => 'status',
-					'reverse' => 'status DESC',
-				),
-			),
-		),
-		'additional_rows' => array(
-			array(
-				'position' => 'after_title',
-				'value' => $txt['hooks_disable_instructions'] . '<br />
-					' . $txt['hooks_disable_legend'] . ':
-									<ul style="list-style: none;">
-					<li><img src="' . $settings['images_url'] . '/admin/post_moderation_allow.png" alt="' . $txt['hooks_active'] . '" title="' . $txt['hooks_active'] . '" /> ' . $txt['hooks_disable_legend_exists'] . '</li>
-					<li><img src="' . $settings['images_url'] . '/admin/post_moderation_moderate.png" alt="' . $txt['hooks_disabled'] . '" title="' . $txt['hooks_disabled'] . '" /> ' . $txt['hooks_disable_legend_disabled'] . '</li>
-					<li><img src="' . $settings['images_url'] . '/admin/post_moderation_deny.png" alt="' . $txt['hooks_missing'] . '" title="' . $txt['hooks_missing'] . '" /> ' . $txt['hooks_disable_legend_missing'] . '</li>
-				</ul>'
-			),
-		),
-	);
-
-	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_url\'] . \';\' . $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_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
-			'name' => 'list_integration_hooks',
-		);
-	}
-
-
-	require_once($sourcedir . '/Subs-List.php');
-	createList($list_options);
-
-	$context['page_title'] = $txt['hooks_title_list'];
-	$context['sub_template'] = 'show_list';
-	$context['default_list'] = 'list_integration_hooks';
-}
-
-/**
- * Gets all of the files in a directory and its chidren directories
- *
- * @param type $dir_path
- * @return array
- */
-function get_files_recursive($dir_path)
-{
-	$files = array();
-
-	if ($dh = opendir($dir_path))
-	{
-		while (($file = readdir($dh)) !== false)
-		{
-			if ($file != '.' && $file != '..')
-			{
-				if (is_dir($dir_path . '/' . $file))
-					$files = array_merge($files, get_files_recursive($dir_path . '/' . $file));
-				else
-					$files[] = array('dir' => $dir_path, 'name' => $file);
-			}
-		}
-	}
-	closedir($dh);
-
-	return $files;
-}
-
-/**
- * Callback function for the integration hooks list (list_integration_hooks)
- * Gets all of the hooks in the system and their status
- * Would be better documented if Ema was not lazy
- *
- * @param type $start
- * @param type $per_page
- * @param type $sort
- * @return array
- */
-function get_integration_hooks_data($start, $per_page, $sort)
-{
-	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl, $modSettings;
-
-	$hooks = $temp_hooks = get_integration_hooks();
-	$hooks_data = $temp_data = $hook_status = array();
-
-	$files = get_files_recursive($sourcedir);
-	if (!empty($files))
-	{
-		foreach ($files as $file)
-		{
-			if (is_file($file['dir'] . '/' . $file['name']) && substr($file['name'], -4) === '.php')
-			{
-				$fp = fopen($file['dir'] . '/' . $file['name'], 'rb');
-				$fc = fread($fp, filesize($file['dir'] . '/' . $file['name']));
-				fclose($fp);
-
-				foreach ($temp_hooks as $hook => $functions)
-				{
-					foreach ($functions as $function_o)
-					{
-						$hook_name = str_replace(']', '', $function_o);
-						if (strpos($hook_name, '::') !== false)
-						{
-							$function = explode('::', $hook_name);
-							$function = $function[1];
-						}
-						else
-							$function = $hook_name;
-						$function = explode(':', $function);
-						$function = $function[0];
-
-						if (substr($hook, -8) === '_include')
-						{
-							$hook_status[$hook][$function]['exists'] = file_exists(strtr(trim($function), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
-							// I need to know if there is at least one function called in this file.
-							$temp_data['include'][basename($function)] = array('hook' => $hook, 'function' => $function);
-							unset($temp_hooks[$hook][$function_o]);
-						}
-						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($function) . '(') !== false)
-						{
-							$hook_status[$hook][$hook_name]['exists'] = true;
-							$hook_status[$hook][$hook_name]['in_file'] = $file['name'];
-							// I want to remember all the functions called within this file (to check later if they are enabled or disabled and decide if the integrare_*_include of that file can be disabled too)
-							$temp_data['function'][$file['name']][] = $function_o;
-							unset($temp_hooks[$hook][$function_o]);
-						}
-					}
-				}
-			}
-		}
-	}
-
-	$sort_types = array(
-		'hook_name' => array('hook', SORT_ASC),
-		'hook_name DESC' => array('hook', SORT_DESC),
-		'function_name' => array('function', SORT_ASC),
-		'function_name DESC' => array('function', SORT_DESC),
-		'file_name' => array('file_name', SORT_ASC),
-		'file_name DESC' => array('file_name', SORT_DESC),
-		'status' => array('status', SORT_ASC),
-		'status DESC' => array('status', SORT_DESC),
-	);
-
-	$sort_options = $sort_types[$sort];
-	$sort = array();
-	$hooks_filters = array();
-
-	foreach ($hooks as $hook => $functions)
-	{
-		$hooks_filters[] = '<option ' . ($context['current_filter'] == $hook ? 'selected="selected" ' : '') . 'onclick="window.location = \'' . $scripturl . '?action=admin;area=modsettings;sa=hooks;filter=' . $hook . '\';">' . $hook . '</option>';
-		foreach ($functions as $function)
-		{
-			$enabled = strstr($function, ']') === false;
-			$function = str_replace(']', '', $function);
-
-			// This is a not an include and the function is included in a certain file (if not it doesn't exists so don't care)
-			if (substr($hook, -8) !== '_include' && isset($hook_status[$hook][$function]['in_file']))
-			{
-				$current_hook = isset($temp_data['include'][$hook_status[$hook][$function]['in_file']]) ? $temp_data['include'][$hook_status[$hook][$function]['in_file']] : '';
-				$enabled = false;
-
-				// Checking all the functions within this particular file
-				// if any of them is enable then the file *must* be included and the integrate_*_include hook cannot be disabled
-				foreach ($temp_data['function'][$hook_status[$hook][$function]['in_file']] as $func)
-					$enabled = $enabled || strstr($func, ']') !== false;
-
-				if (!$enabled &&  !empty($current_hook))
-					$hook_status[$current_hook['hook']][$current_hook['function']]['enabled'] = true;
-			}
-		}
-	}
-
-	if (!empty($hooks_filters))
-		$context['insert_after_template'] .= '
-		<script type="text/javascript"><!-- // --><![CDATA[
-			var hook_name_header = document.getElementById(\'header_list_integration_hooks_hook_name\');
-			hook_name_header.innerHTML += ' . JavaScriptEscape('<select style="margin-left:15px;"><option>---</option><option onclick="window.location = \'' . $scripturl . '?action=admin;area=modsettings;sa=hooks\';">' . $txt['hooks_reset_filter'] . '</option>' . implode('', $hooks_filters) . '</select>'). ';
-		// ]]></script>';
-
-	$temp_data = array();
-	$id = 0;
-
-	foreach ($hooks as $hook => $functions)
-	{
-		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
-		{
-			foreach ($functions as $function)
-			{
-				$enabled = strstr($function, ']') === false;
-				$function = str_replace(']', '', $function);
-				$hook_exists = !empty($hook_status[$hook][$function]['exists']);
-				$file_name = isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ((substr($hook, -8) === '_include') ? 'zzzzzzzzz' : 'zzzzzzzza');
-				$sort[] = $$sort_options[0];
-
-				if (strpos($function, '::') !== false)
-				{
-					$function = explode('::', $function);
-					$function = $function[1];
-				}
-				$exploded = explode(':', $function);
-
-				$temp_data[] = array(
-					'id' => 'hookid_' . $id++,
-					'hook_name' => $hook,
-					'function_name' => $function,
-					'real_function' => $exploded[0],
-					'included_file' => isset($exploded[1]) ? strtr(trim($exploded[1]), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])) : '',
-					'file_name' => (isset($hook_status[$hook][$function]['in_file']) ? $hook_status[$hook][$function]['in_file'] : ''),
-					'hook_exists' => $hook_exists,
-					'status' => $hook_exists ? ($enabled ? 'allow' : 'moderate') : 'deny',
-					'img_text' => $txt['hooks_' . ($hook_exists ? ($enabled ? 'active' : 'disabled') : 'missing')],
-					'enabled' => $enabled,
-					'can_be_disabled' => !empty($modSettings['handlinghooks_enabled']) && !isset($hook_status[$hook][$function]['enabled']),
-				);
-			}
-		}
-	}
-
-	array_multisort($sort, $sort_options[1], $temp_data);
-
-	$counter = 0;
-	$start++;
-
-	foreach ($temp_data as $data)
-	{
-		if (++$counter < $start)
-			continue;
-		elseif ($counter == $start + $per_page)
-			break;
-
-		$hooks_data[] = $data;
-	}
-
-	return $hooks_data;
-}
-
-/**
- * Simply returns the total count of integraion hooks
- * Used but the intergation hooks list function (list_integration_hooks)
- *
- * @global type $context
- * @return int
- */
-function get_integration_hooks_count()
-{
-	global $context;
-
-	$hooks = get_integration_hooks();
-	$hooks_count = 0;
-
-	$context['filter'] = false;
-	if (isset($_GET['filter']))
-		$context['filter'] = $_GET['filter'];
-
-	foreach ($hooks as $hook => $functions)
-	{
-		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
-			$hooks_count += count($functions);
-	}
-
-	return $hooks_count;
-}
-
-/**
- * Parses modSettings to create integration hook array
- *
- * @staticvar type $integration_hooks
- * @return type
- */
-function get_integration_hooks()
-{
-	global $modSettings;
-	static $integration_hooks;
-
-	if (!isset($integration_hooks))
-	{
-		$integration_hooks = array();
-		foreach ($modSettings as $key => $value)
-		{
-			if (!empty($value) && substr($key, 0, 10) === 'integrate_')
-				$integration_hooks[$key] = explode(',', $value);
-		}
-	}
-
-	return $integration_hooks;
-}
-
 ?>

+ 57 - 24
Sources/ModerationCenter.php

@@ -224,11 +224,12 @@ function ModerationHome()
 	$context['page_title'] = $txt['moderation_center'];
 	$context['sub_template'] = 'moderation_center';
 
+	// Handle moderators notes.
+	ModBlockNotes();
+
 	// Load what blocks the user actually can see...
-	$valid_blocks = array(
-		'n' => 'LatestNews',
-		'p' => 'Notes',
-	);
+	$valid_blocks = array();
+
 	if ($context['can_moderate_groups'])
 		$valid_blocks['g'] = 'GroupRequests';
 	if ($context['can_moderate_boards'])
@@ -256,19 +257,6 @@ function ModerationHome()
 	}
 }
 
-/**
- * Just prepares the time stuff for the simple machines latest news.
- */
-function ModBlockLatestNews()
-{
-	global $context, $user_info;
-
-	$context['time_format'] = urlencode($user_info['time_format']);
-
-	// Return the template to use.
-	return 'latest_news';
-}
-
 /**
  * Show a list of the most active watched users.
  */
@@ -320,7 +308,7 @@ function ModBlockNotes()
 	global $context, $smcFunc, $scripturl, $txt, $user_info;
 
 	// Are we saving a note?
-	if (isset($_POST['makenote']) && isset($_POST['new_note']))
+	if (isset($_GET['modnote']) && isset($_POST['makenote']) && isset($_POST['new_note']))
 	{
 		checkSession();
 
@@ -427,7 +415,7 @@ function ModBlockNotes()
 		$context['notes'][] = array(
 			'author' => array(
 				'id' => $note['id_member'],
-				'link' => $note['id_member'] ? ('<a href="' . $scripturl . '?action=profile;u=' . $note['id_member'] . '" title="' . $txt['on'] . ' ' . strip_tags(timeformat($note['log_time'])) . '">' . $note['member_name'] . '</a>') : $note['member_name'],
+				'link' => $note['id_member'] ? ('<a href="' . $scripturl . '?action=profile;u=' . $note['id_member'] . '">' . $note['member_name'] . '</a>') : $note['member_name'],
 			),
 			'time' => timeformat($note['log_time']),
 			'text' => parse_bbc($note['body']),
@@ -560,6 +548,9 @@ function ReportedPosts()
 
 	loadTemplate('ModerationCenter');
 
+	// Set an empty var for the server response.
+	$context['report_post_action'] = '';
+
 	// Put the open and closed options into tabs, because we can...
 	$context[$context['moderation_menu_name']]['tab_data'] = array(
 		'title' => $txt['mc_reported_posts'],
@@ -601,6 +592,9 @@ function ReportedPosts()
 			)
 		);
 
+		// Tell the user about it.
+		$context['report_post_action'] = isset($_GET['ignore']) ? (!empty($_GET['ignore']) ? 'ignore' : 'unignore') : (!empty($_GET['close']) ? 'close' : 'open');
+
 		// Time to update.
 		updateSettings(array('last_mod_report_action' => time()));
 		recountOpenReports();
@@ -631,6 +625,9 @@ function ReportedPosts()
 			updateSettings(array('last_mod_report_action' => time()));
 			recountOpenReports();
 		}
+
+		// Go on and tell the result.
+		$context['report_post_action'] = 'close_all';
 	}
 
 	// How many entries are we viewing?
@@ -667,13 +664,20 @@ function ReportedPosts()
 	);
 	$context['reports'] = array();
 	$report_ids = array();
+	$report_boards_ids = array();
 	for ($i = 0; $row = $smcFunc['db_fetch_assoc']($request); $i++)
 	{
 		$report_ids[] = $row['id_report'];
+		$report_boards_ids[] = $row['id_board'];
 		$context['reports'][$row['id_report']] = array(
 			'id' => $row['id_report'],
 			'alternate' => $i % 2,
-			'topic_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
+			'topic' => array(
+				'id' => $row['id_topic'],
+				'id_msg' => $row['id_msg'],
+				'id_board' => $row['id_board'],
+				'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
+			),
 			'report_href' => $scripturl . '?action=moderate;area=reports;report=' . $row['id_report'],
 			'author' => array(
 				'id' => $row['id_author'],
@@ -693,6 +697,29 @@ function ReportedPosts()
 	}
 	$smcFunc['db_free_result']($request);
 
+	// Get the names of boards those topics are in. Slightly faster this way.
+	if (!empty($report_boards_ids))
+	{
+		$report_boards_ids = array_unique($report_boards_ids);
+		$board_names = array();
+		$request = $smcFunc['db_query']('', '
+			SELECT id_board, name
+			FROM {db_prefix}boards
+			WHERE id_board IN ({array_int:boards})',
+			array(
+				'boards' => $report_boards_ids,
+			)
+		);
+
+		while ($row = $smcFunc['db_fetch_assoc']($request))
+			$board_names[$row['id_board']] = $row['name'];
+		$smcFunc['db_free_result']($request);
+
+		foreach ($context['reports'] as $id_report => $report)
+			if (!empty($board_names[$report['topic']['id_board']]))
+				$context['reports'][$id_report]['topic']['board_name'] = $board_names[$report['topic']['id_board']];
+	}
+
 	// Now get all the people who reported it.
 	if (!empty($report_ids))
 	{
@@ -722,6 +749,14 @@ function ReportedPosts()
 		}
 		$smcFunc['db_free_result']($request);
 	}
+
+	// Get the boards where the current user can remove any message.
+	$context['report_remove_any_boards'] = $user_info['is_admin'] ? $report_boards_ids : array_intersect($report_boards_ids, boardsAllowedTo('remove_any'));
+	$context['report_manage_bans'] = allowedTo('manage_bans');
+
+	// Do we deleted a message?
+	if (isset($_REQUEST['done']))
+		$context['report_post_action'] = 'message_deleted';
 }
 
 /**
@@ -2066,10 +2101,8 @@ function ModerationSettings()
 	);
 
 	// What blocks can this user see?
-	$context['homepage_blocks'] = array(
-		'n' => $txt['mc_prefs_latest_news'],
-		'p' => $txt['mc_notes'],
-	);
+	$context['homepage_blocks'] = array();
+
 	if ($context['can_moderate_groups'])
 		$context['homepage_blocks']['g'] = $txt['mc_group_requests'];
 	if ($context['can_moderate_boards'])

+ 1 - 1
Sources/News.php

@@ -368,7 +368,7 @@ function fix_possible_url($val)
 	if (empty($modSettings['queryless_urls']) || ($context['server']['is_cgi'] && ini_get('cgi.fix_pathinfo') == 0 && @get_cfg_var('cgi.fix_pathinfo') == 0) || (!$context['server']['is_apache'] && !$context['server']['is_lighttpd']))
 		return $val;
 
-	$val = preg_replace('/^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+)(#[^"]*)?$/e', '\'\' . $scripturl . \'/\' . strtr(\'$1\', \'&;=\', \'//,\') . \'.html$2\'', $val);
+	$val = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+)(#[^"]*)?$~', create_function('$m', 'global $scripturl; return $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html\' . (isset($m[2]) ? $m[2] : "");'), $val);
 	return $val;
 }
 

+ 15 - 10
Sources/PersonalMessage.php

@@ -175,7 +175,7 @@ function MessageMain()
 	$context['can_issue_warning'] = in_array('w', $context['admin_features']) && allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1;
 
 	// Are PM drafts enabled?
-	$context['drafts_pm_save'] = !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
+	$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');
 
 	// Build the linktree for all the actions...
@@ -205,7 +205,10 @@ function MessageMain()
 	);
 
 	if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
+	{
+		$_REQUEST['sa'] = '';
 		MessageFolder();
+	}
 	else
 	{
 		if (!isset($_REQUEST['xml']))
@@ -244,7 +247,7 @@ function messageIndexBar($area)
 					'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']),
+					'enabled' => !empty($modSettings['drafts_pm_enabled']),
 				),
 			),
 		),
@@ -1169,7 +1172,9 @@ function MessageSearch2()
 			foreach ($possible_users as $k => $v)
 			{
 				$where_params['name_' . $k] = $v;
-				$where_clause[] = 'real_name LIKE {string:name_' . $k . '}';
+				$where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}';
+				if (!isset($where_params['real_name']))
+					$where_params['real_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
 			}
 
 			// Who matches those criteria?
@@ -1186,17 +1191,19 @@ function MessageSearch2()
 				$userQuery = '';
 			elseif ($smcFunc['db_num_rows']($request) == 0)
 			{
-				$userQuery = 'AND pm.id_member_from = 0 AND (pm.from_name LIKE {raw:guest_user_name_implode})';
-				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR pm.from_name LIKE \'', $possible_users) . '\'';
+				$userQuery = 'AND pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})';
+				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
+				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
 			}
 			else
 			{
 				$memberlist = array();
 				while ($row = $smcFunc['db_fetch_assoc']($request))
 					$memberlist[] = $row['id_member'];
-				$userQuery = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND (pm.from_name LIKE {raw:guest_user_name_implode})))';
-				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR pm.from_name LIKE \'', $possible_users) . '\'';
+				$userQuery = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})))';
+				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
 				$searchq_parameters['member_list'] = $memberlist;
+				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
 			}
 			$smcFunc['db_free_result']($request);
 		}
@@ -1791,7 +1798,6 @@ function MessagePost()
 	$context['subject'] = $form_subject;
 	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
 	$context['post_error'] = array();
-	$context['copy_to_outbox'] = !empty($options['copy_to_outbox']);
 
 	// And build the link tree.
 	$context['linktree'][] = array(
@@ -1915,7 +1921,6 @@ function messagePostError($error_types, $named_recipients, $recipient_ids = arra
 	// Set everything up like before....
 	$context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
 	$context['message'] = isset($_REQUEST['message']) ? str_replace(array('  '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
-	$context['copy_to_outbox'] = !empty($_REQUEST['outbox']);
 	$context['reply'] = !empty($_REQUEST['replied_to']);
 
 	if ($context['reply'])
@@ -2291,7 +2296,7 @@ function MessagePost2()
 
 	// Do the actual sending of the PM.
 	if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
-		$context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], !empty($_REQUEST['outbox']), null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
+		$context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], true, null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
 	else
 		$context['send_log'] = array(
 			'sent' => array(),

+ 10 - 10
Sources/Post.php

@@ -493,7 +493,7 @@ function Post($post_errors = array())
 		// Previewing an edit?
 		if (isset($_REQUEST['msg']) && !empty($topic))
 		{
-			// Get the existing message.
+			// Get the existing message. Previewing.
 			$request = $smcFunc['db_query']('', '
 				SELECT
 					m.id_member, m.modified_time, m.smileys_enabled, m.body,
@@ -607,7 +607,7 @@ function Post($post_errors = array())
 	{
 		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
 
-		// Get the existing message.
+		// Get the existing message. Editing.
 		$request = $smcFunc['db_query']('', '
 			SELECT
 				m.id_member, m.modified_time, m.modified_name, m.smileys_enabled, m.body,
@@ -766,7 +766,7 @@ function Post($post_errors = array())
 				{
 					// It goes 0 = outside, 1 = begin tag, 2 = inside, 3 = close tag, repeat.
 					if ($i % 4 == 0)
-						$parts[$i] = preg_replace('~\[html\](.+?)\[/html\]~ise', '\'[html]\' . preg_replace(\'~<br\s?/?' . '>~i\', \'&lt;br /&gt;<br />\', \'$1\') . \'[/html]\'', $parts[$i]);
+						$parts[$i] = preg_replace_callback('~\[html\](.+?)\[/html\]~is', create_function('$m', ' return \'[html]\' . preg_replace(\'~<br\s?/?' . '>~i\', \'&lt;br /&gt;<br />\', "$m[1]") . \'[/html]\';'), $parts[$i]);
 				}
 				$form_message = implode('', $parts);
 			}
@@ -1034,7 +1034,7 @@ function Post($post_errors = array())
 	$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_save'] = !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
@@ -1208,7 +1208,7 @@ function Post2()
 	loadLanguage('Post');
 
 	// Drafts enabled and needed?
-	if (!empty($modSettings['drafts_enabled']) && (isset($_POST['save_draft']) || isset($_POST['id_draft'])))
+	if (!empty($modSettings['drafts_post_enabled']) && (isset($_POST['save_draft']) || isset($_POST['id_draft'])))
 		require_once($sourcedir . '/Drafts.php');
 
 	// First check to see if they are trying to delete any current attachments.
@@ -1331,7 +1331,7 @@ function Post2()
 			unset($_POST['sticky']);
 
 		// If drafts are enabled, then pass this off
-		if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		if (!empty($modSettings['drafts_post_enabled']) && isset($_POST['save_draft']))
 		{
 			SaveDraft($post_errors);
 			return Post();
@@ -1376,7 +1376,7 @@ function Post2()
 			unset($_POST['sticky']);
 
 		// Saving your new topic as a draft first?
-		if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		if (!empty($modSettings['drafts_post_enabled']) && isset($_POST['save_draft']))
 		{
 			SaveDraft($post_errors);
 			return Post();
@@ -1459,7 +1459,7 @@ function Post2()
 		}
 
 		// If drafts are enabled, then lets send this off to save
-		if (!empty($modSettings['drafts_enabled']) && isset($_POST['save_draft']))
+		if (!empty($modSettings['drafts_post_enabled']) && isset($_POST['save_draft']))
 		{
 			SaveDraft($post_errors);
 			return Post();
@@ -1695,7 +1695,7 @@ function Post2()
 				'tmp_name' => $attachment['tmp_name'],
 				'size' => isset($attachment['size']) ? $attachment['size'] : 0,
 				'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
-				'id_folder' => $attachment['id_folder'],
+				'id_folder' => isset($attachment['id_folder']) ? $attachment['id_folder'] : $modSettings['currentAttachmentUploadDir'],
 				'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
 				'errors' => $attachment['errors'],
 			);
@@ -1831,7 +1831,7 @@ function Post2()
 	}
 
 	// If we had a draft for this, its time to remove it since it was just posted
-	if (!empty($modSettings['drafts_enabled']) && !empty($_POST['id_draft']))
+	if (!empty($modSettings['drafts_post_enabled']) && !empty($_POST['id_draft']))
 		DeleteDraft($_POST['id_draft']);
 
 	// Editing or posting an event?

+ 2 - 2
Sources/Profile-Modify.php

@@ -575,13 +575,13 @@ function loadProfileFields($force_reload = false)
 			'permission' => 'profile_extra',
 			'is_dummy' => true,
 			'preload' => create_function('', '
-				global $context, $user_info;
+				global $context, $user_info, $modSettings;
 
 				loadLanguage(\'Settings\');
 
 				$context[\'allow_no_censored\'] = false;
 				if ($user_info[\'is_admin\'] || $context[\'user\'][\'is_owner\'])
-					$context[\'allow_no_censored\'] = $modSettings[\'allow_no_censored\'];
+					$context[\'allow_no_censored\'] = !empty($modSettings[\'allow_no_censored\']);
 
 				return true;
 			'),

+ 1 - 1
Sources/Profile.php

@@ -121,7 +121,7 @@ function ModifyProfile($post_errors = array())
 					'label' => $txt['drafts_show'],
 					'file' => 'Drafts.php',
 					'function' => 'showProfileDrafts',
-					'enabled' => !empty($modSettings['drafts_enabled']) && $context['user']['is_owner'],
+					'enabled' => !empty($modSettings['drafts_post_enabled']) && $context['user']['is_owner'],
 					'permission' => array(
 						'own' => 'profile_view_own',
 						'any' =>  array(),

+ 3 - 3
Sources/QueryString.php

@@ -607,7 +607,7 @@ function ob_sessrewrite($buffer)
 	// Do nothing if the session is cookied, or they are a crawler - guests are caught by redirectexit().  This doesn't work below PHP 4.3.0, because it makes the output buffer bigger.
 	// @todo smflib
 	if (empty($_COOKIE) && SID != '' && !isBrowser('possibly_robot'))
-		$buffer = preg_replace('/"' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', '"' . $scripturl . '?' . SID . '&amp;', $buffer);
+		$buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', '"' . $scripturl . '?' . SID . '&amp;', $buffer);
 	// Debugging templates, are we?
 	elseif (isset($_GET['debug']))
 		$buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '\\??/', '"' . $scripturl . '?debug;', $buffer);
@@ -617,9 +617,9 @@ function ob_sessrewrite($buffer)
 	{
 		// Let's do something special for session ids!
 		if (defined('SID') && SID != '')
-			$buffer = preg_replace('/"' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#"]+?)(#[^"]*?)?"/e', "'\"' . \$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html?' . SID . '\$2\"'", $buffer);
+			$buffer = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', create_function('$m', 'global $scripturl; return \'"\' . $scripturl . "/" . strtr("$m[1]", \'&;=\', \'//,\') . ".html?" . SID . (isset($m[2]) ? $m[2] : "") . \'"\';'), $buffer);
 		else
-			$buffer = preg_replace('/"' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?"/e', "'\"' . \$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2\"'", $buffer);
+			$buffer = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', create_function('$m', 'global $scripturl; return \'"\' . $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html\' . (isset($m[2]) ? $m[2] : "") . \'"\';'), $buffer );
 	}
 
 	// Return the changed buffer.

+ 20 - 18
Sources/RemoveTopic.php

@@ -136,7 +136,9 @@ function DeleteMessage()
 		logAction('delete', array('topic' => $topic, 'subject' => $subject, 'member' => $poster, 'board' => $board));
 
 	// We want to redirect back to recent action.
-	if (isset($_REQUEST['recent']))
+	if (isset($_REQUEST['modcenter']))
+		redirectexit('action=moderate;area=reports;done');
+	elseif (isset($_REQUEST['recent']))
 		redirectexit('action=recent');
 	elseif (isset($_REQUEST['profile'], $_REQUEST['start'], $_REQUEST['u']))
 		redirectexit('action=profile;u=' . $_REQUEST['u'] . ';area=showposts;start=' . $_REQUEST['start']);
@@ -658,23 +660,6 @@ function removeMessage($message, $decreasePostCount = true)
 			isAllowedTo('approve_posts');
 	}
 
-	// Close any moderation reports for this message.
-	$smcFunc['db_query']('', '
-		UPDATE {db_prefix}log_reported
-		SET closed = {int:is_closed}
-		WHERE id_msg = {int:id_msg}',
-		array(
-			'is_closed' => 1,
-			'id_msg' => $message,
-		)
-	);
-	if ($smcFunc['db_affected_rows']() != 0)
-	{
-		require_once($sourcedir . '/ModerationCenter.php');
-		updateSettings(array('last_mod_report_action' => time()));
-		recountOpenReports();
-	}
-
 	// Delete the *whole* topic, but only if the topic consists of one message.
 	if ($row['id_first_msg'] == $message)
 	{
@@ -993,6 +978,23 @@ function removeMessage($message, $decreasePostCount = true)
 	else
 		updateLastMessages($row['id_board']);
 
+	// Close any moderation reports for this message.
+	$smcFunc['db_query']('', '
+		UPDATE {db_prefix}log_reported
+		SET closed = {int:is_closed}
+		WHERE id_msg = {int:id_msg}',
+		array(
+			'is_closed' => 1,
+			'id_msg' => $message,
+		)
+	);
+	if ($smcFunc['db_affected_rows']() != 0)
+	{
+		require_once($sourcedir . '/ModerationCenter.php');
+		updateSettings(array('last_mod_report_action' => time()));
+		recountOpenReports();
+	}
+
 	return false;
 }
 

+ 17 - 5
Sources/ScheduledTasks.php

@@ -1246,7 +1246,7 @@ function loadEssentialThemeData()
  */
 function scheduled_fetchSMfiles()
 {
-	global $sourcedir, $txt, $language, $settings, $forum_version, $modSettings, $smcFunc;
+	global $sourcedir, $txt, $language, $settings, $forum_version, $modSettings, $smcFunc, $context;
 
 	// What files do we want to get
 	$request = $smcFunc['db_query']('', '
@@ -1285,9 +1285,10 @@ function scheduled_fetchSMfiles()
 		// Get the file
 		$file_data = fetch_web_data($url);
 
-		// If we got an error - give up - the site might be down.
+		// If we got an error - give up - the site might be down. And if we should happen to be coming from elsewhere, let's also make a note of it.
 		if ($file_data === false)
 		{
+			$context['scheduled_errors']['fetchSMfiles'][] = sprintf($txt['st_cannot_retrieve_file'], $url);
 			log_error(sprintf($txt['st_cannot_retrieve_file'], $url));
 			return false;
 		}
@@ -1663,7 +1664,7 @@ function scheduled_paid_subscriptions()
  */
 function scheduled_remove_temp_attachments()
 {
-	global $modSettings;
+	global $modSettings, $context, $txt;
 
 	// We need to know where this thing is going.
 	if (!empty($modSettings['currentAttachmentUploadDir']))
@@ -1681,7 +1682,16 @@ function scheduled_remove_temp_attachments()
 
 	foreach ($attach_dirs as $attach_dir)
 	{
-		$dir = @opendir($attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical');
+		$dir = @opendir($attach_dir);
+		if (!$dir)
+		{
+			loadEssentialThemeData();
+			loadLanguage('Post');
+			$context['scheduled_errors']['remove_temp_attachments'][] = $txt['cant_access_upload_path'] . ' (' . $attach_dir . ')';
+			log_error($txt['cant_access_upload_path'] . ' (' . $attach_dir . ')', 'critical');
+			return false;
+		}
+
 		while ($file = readdir($dir))
 		{
 			if ($file == '.' || $file == '..')
@@ -1696,6 +1706,8 @@ function scheduled_remove_temp_attachments()
 		}
 		closedir($dir);
 	}
+
+	return true;
 }
 
 /**
@@ -1747,7 +1759,7 @@ function scheduled_remove_old_drafts()
 		return true;
 
 	// init
-	$drafts= array();
+	$drafts = array();
 
 	// We need this for lanaguage items
 	loadEssentialThemeData();

+ 2 - 2
Sources/Security.php

@@ -364,7 +364,7 @@ function is_not_banned($forceCheck = false)
 		writeLog(true);
 
 		// You banned, sucka!
-		fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_access']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_access']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']), 'user');
+		fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_access']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_access']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']), !empty($modSettings['log_ban_hits']) ? 'ban' : false);
 
 		// If we get here, something's gone wrong.... but let's try anyway.
 		trigger_error('Hacking attempt...', E_USER_ERROR);
@@ -410,7 +410,7 @@ function is_not_banned($forceCheck = false)
 		require_once($sourcedir . '/LogInOut.php');
 		Logout(true, false);
 
-		fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_login']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_login']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']) . '<br />' . $txt['ban_continue_browse'], 'user');
+		fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_login']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_login']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']) . '<br />' . $txt['ban_continue_browse'], !empty($modSettings['log_ban_hits']) ? 'ban' : false);
 	}
 
 	// Fix up the banning permissions.

+ 1 - 1
Sources/SendTopic.php

@@ -545,7 +545,7 @@ function ReportToModerator2()
 	$real_mods = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
 		$real_mods[] = $row['id_member'];
-	$smcFunc['db_free_result']($request2);
+	$smcFunc['db_free_result']($request);
 
 	// Get any additional members who are in groups assigned to moderate this board
 	$request = $smcFunc['db_query']('', '

+ 38 - 34
Sources/Stats.php

@@ -149,43 +149,47 @@ function DisplayStats()
 	$context['latest_member'] = &$context['common_stats']['latest_member'];
 
 	// Male vs. female ratio - let's calculate this only every four minutes.
-	if (($context['gender'] = cache_get_data('stats_gender', 240)) == null)
+	$disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
+	if (!in_array('gender', $disabled_fields))
 	{
-		$result = $smcFunc['db_query']('', '
-			SELECT COUNT(*) AS total_members, gender
-			FROM {db_prefix}members
-			GROUP BY gender',
-			array(
-			)
-		);
-		$context['gender'] = array();
-		while ($row = $smcFunc['db_fetch_assoc']($result))
+		if (($context['gender'] = cache_get_data('stats_gender', 240)) == null)
 		{
-			// Assuming we're telling... male or female?
-			if (!empty($row['gender']))
-				$context['gender'][$row['gender'] == 2 ? 'females' : 'males'] = $row['total_members'];
+			$result = $smcFunc['db_query']('', '
+				SELECT COUNT(*) AS total_members, gender
+				FROM {db_prefix}members
+				GROUP BY gender',
+				array(
+				)
+			);
+			$context['gender'] = array();
+			while ($row = $smcFunc['db_fetch_assoc']($result))
+			{
+				// Assuming we're telling... male or female?
+				if (!empty($row['gender']))
+					$context['gender'][$row['gender'] == 2 ? 'females' : 'males'] = $row['total_members'];
+			}
+			$smcFunc['db_free_result']($result);
+
+			// Set these two zero if the didn't get set at all.
+			if (empty($context['gender']['males']))
+				$context['gender']['males'] = 0;
+			if (empty($context['gender']['females']))
+				$context['gender']['females'] = 0;
+
+			// Try and come up with some "sensible" default states in case of a non-mixed board.
+			if ($context['gender']['males'] == $context['gender']['females'])
+				$context['gender']['ratio'] = '1:1';
+			elseif ($context['gender']['males'] == 0)
+				$context['gender']['ratio'] = '0:1';
+			elseif ($context['gender']['females'] == 0)
+				$context['gender']['ratio'] = '1:0';
+			elseif ($context['gender']['males'] > $context['gender']['females'])
+				$context['gender']['ratio'] = round($context['gender']['males'] / $context['gender']['females'], 1) . ':1';
+			elseif ($context['gender']['females'] > $context['gender']['males'])
+				$context['gender']['ratio'] = '1:' . round($context['gender']['females'] / $context['gender']['males'], 1);
+
+			cache_put_data('stats_gender', $context['gender'], 240);
 		}
-		$smcFunc['db_free_result']($result);
-
-		// Set these two zero if the didn't get set at all.
-		if (empty($context['gender']['males']))
-			$context['gender']['males'] = 0;
-		if (empty($context['gender']['females']))
-			$context['gender']['females'] = 0;
-
-		// Try and come up with some "sensible" default states in case of a non-mixed board.
-		if ($context['gender']['males'] == $context['gender']['females'])
-			$context['gender']['ratio'] = '1:1';
-		elseif ($context['gender']['males'] == 0)
-			$context['gender']['ratio'] = '0:1';
-		elseif ($context['gender']['females'] == 0)
-			$context['gender']['ratio'] = '1:0';
-		elseif ($context['gender']['males'] > $context['gender']['females'])
-			$context['gender']['ratio'] = round($context['gender']['males'] / $context['gender']['females'], 1) . ':1';
-		elseif ($context['gender']['females'] > $context['gender']['males'])
-			$context['gender']['ratio'] = '1:' . round($context['gender']['females'] / $context['gender']['males'], 1);
-
-		cache_put_data('stats_gender', $context['gender'], 240);
 	}
 
 	$date = strftime('%Y-%m-%d', forum_time(false));

+ 3 - 2
Sources/Subs-Auth.php

@@ -130,7 +130,7 @@ function url_parts($local, $global)
 
 	// Globalize cookies across domains (filter out IP-addresses)?
 	elseif ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
-			$parsed_url['host'] = '.' . $parts[1];
+		$parsed_url['host'] = '.' . $parts[1];
 
 	// We shouldn't use a host at all if both options are off.
 	elseif (!$local && !$global)
@@ -492,11 +492,12 @@ function RequestMembers()
 	$request = $smcFunc['db_query']('', '
 		SELECT real_name
 		FROM {db_prefix}members
-		WHERE real_name LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
+		WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
 			AND id_member IN ({array_int:buddy_list})' : '') . '
 			AND is_activated IN (1, 11)
 		LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'),
 		array(
+			'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
 			'buddy_list' => $user_info['buddies'],
 			'search' => $_REQUEST['search'],
 		)

+ 1 - 1
Sources/Subs-Boards.php

@@ -175,7 +175,7 @@ function MarkRead()
 	}
 	elseif (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'unreadreplies')
 	{
-		// Make sure all the boards are integers!
+		// Make sure all the topics are integers!
 		$topics = array_map('intval', explode('-', $_REQUEST['topics']));
 
 		$smcFunc['db_query']('', '

+ 6 - 0
Sources/Subs-Db-mysql.php

@@ -139,9 +139,15 @@ function smf_db_replacement__callback($matches)
 	if ($matches[1] === 'query_wanna_see_board')
 		return $user_info['query_wanna_see_board'];
 
+	if ($matches[1] === 'empty')
+		return '\'\'';
+
 	if (!isset($matches[2]))
 		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
 
+	if ($matches[1] === 'literal')
+		return mysql_real_escape_string($matches[2], $connection);
+
 	if (!isset($values[$matches[2]]))
 		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
 

+ 6 - 0
Sources/Subs-Db-mysqli.php

@@ -161,9 +161,15 @@ function smf_db_replacement__callback($matches)
 	if ($matches[1] === 'query_wanna_see_board')
 		return $user_info['query_wanna_see_board'];
 
+	if ($matches[1] === 'empty')
+		return '\'\'';
+
 	if (!isset($matches[2]))
 		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
 
+	if ($matches[1] === 'literal')
+		return mysqli_real_escape_string($connection, $matches[2]);
+
 	if (!isset($values[$matches[2]]))
 		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
 

+ 6 - 0
Sources/Subs-Db-postgresql.php

@@ -133,9 +133,15 @@ function smf_db_replacement__callback($matches)
 	if ($matches[1] === 'query_wanna_see_board')
 		return $user_info['query_wanna_see_board'];
 
+	if ($matches[1] === 'empty')
+		return '\'\'';
+
 	if (!isset($matches[2]))
 		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
 
+	if ($matches[1] === 'literal')
+		return pg_escape_string($matches[2]);
+
 	if (!isset($values[$matches[2]]))
 		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
 

+ 6 - 0
Sources/Subs-Db-sqlite.php

@@ -145,9 +145,15 @@ function smf_db_replacement__callback($matches)
 	if ($matches[1] === 'query_wanna_see_board')
 		return $user_info['query_wanna_see_board'];
 
+	if ($matches[1] === 'empty')
+		return '\'\'';
+
 	if (!isset($matches[2]))
 		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
 
+	if ($matches[1] === 'literal')
+		return sqlite_escape_string($matches[2]);
+
 	if (!isset($values[$matches[2]]))
 		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
 

+ 6 - 0
Sources/Subs-Db-sqlite3.php

@@ -176,9 +176,15 @@ function smf_db_replacement__callback($matches)
 	if ($matches[1] === 'query_wanna_see_board')
 		return $user_info['query_wanna_see_board'];
 
+	if ($matches[1] === 'empty')
+		return '\'\'';
+
 	if (!isset($matches[2]))
 		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
 
+	if ($matches[1] === 'literal')
+		return SQLite::escapeString($matches[2]);
+
 	if (!isset($values[$matches[2]]))
 		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
 

+ 71 - 51
Sources/Subs-Editor.php

@@ -1936,7 +1936,7 @@ function create_control_richedit($editorOptions)
 function create_control_verification(&$verificationOptions, $do_test = false)
 {
 	global $txt, $modSettings, $options, $smcFunc;
-	global $context, $settings, $user_info, $sourcedir, $scripturl;
+	global $context, $settings, $user_info, $sourcedir, $scripturl, $language;
 
 	// First verification means we need to set up some bits...
 	if (empty($context['controls']['verification']))
@@ -1988,23 +1988,33 @@ function create_control_verification(&$verificationOptions, $do_test = false)
 	// If we want questions do we have a cache of all the IDs?
 	if (!empty($thisVerification['number_questions']) && empty($modSettings['question_id_cache']))
 	{
-		if (($modSettings['question_id_cache'] = cache_get_data('verificationQuestionIds', 300)) == null)
+		if (($modSettings['question_id_cache'] = cache_get_data('verificationQuestions', 300)) == null)
 		{
 			$request = $smcFunc['db_query']('', '
-				SELECT id_comment
-				FROM {db_prefix}log_comments
-				WHERE comment_type = {string:ver_test}',
-				array(
-					'ver_test' => 'ver_test',
-				)
+				SELECT id_question, lngfile, question, answers
+				FROM {db_prefix}qanda',
+				array()
+			);
+			$modSettings['question_id_cache'] = array(
+				'questions' => array(),
+				'langs' => array(),
 			);
-			$modSettings['question_id_cache'] = array();
+			// This is like Captain Kirk climbing a mountain in some ways. This is L's fault, mkay? :P
 			while ($row = $smcFunc['db_fetch_assoc']($request))
-				$modSettings['question_id_cache'][] = $row['id_comment'];
+			{
+				$id_question = $row['id_question'];
+				unset ($row['id_question']);
+				// Make them all lowercase. We can't directly use $smcFunc['strtolower'] with array_walk, so do it manually, eh?
+				$row['answers'] = unserialize($row['answers']);
+				foreach ($row['answers'] as $k => $v)
+					$row['answers'][$k] = $smcFunc['strtolower']($v);
+
+				$modSettings['question_id_cache']['questions'][$id_question] = $row;
+				$modSettings['question_id_cache']['langs'][$row['lngfile']][] = $id_question;
+			}
 			$smcFunc['db_free_result']($request);
 
-			if (!empty($modSettings['cache_enable']))
-				cache_put_data('verificationQuestionIds', $modSettings['question_id_cache'], 300);
+			cache_put_data('verificationQuestions', $modSettings['question_id_cache'], 300);
 		}
 	}
 
@@ -2043,24 +2053,27 @@ function create_control_verification(&$verificationOptions, $do_test = false)
 			$verification_errors[] = 'wrong_verification_code';
 		if ($thisVerification['number_questions'])
 		{
-			// Get the answers and see if they are all right!
-			$request = $smcFunc['db_query']('', '
-				SELECT id_comment, recipient_name AS answer
-				FROM {db_prefix}log_comments
-				WHERE comment_type = {string:ver_test}
-					AND id_comment IN ({array_int:comment_ids})',
-				array(
-					'ver_test' => 'ver_test',
-					'comment_ids' => $_SESSION[$verificationOptions['id'] . '_vv']['q'],
-				)
-			);
 			$incorrectQuestions = array();
-			while ($row = $smcFunc['db_fetch_assoc']($request))
+			foreach ($_SESSION[$verificationOptions['id'] . '_vv']['q'] as $q)
 			{
-				if (!isset($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) || trim($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) == '' || trim($smcFunc['htmlspecialchars'](strtolower($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]))) != strtolower($row['answer']))
-					$incorrectQuestions[] = $row['id_comment'];
+				// We don't have this question any more, thus no answers.
+				if (!isset($modSettings['question_id_cache']['questions'][$q]))
+					continue;
+				// This is quite complex. We have our question but it might have multiple answers.
+				// First, did they actually answer this question?
+				if (!isset($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) || trim($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) == '')
+				{
+					$incorrectQuestions[] = $q;
+					continue;
+				}
+				// Second, is their answer in the list of possible answers?
+				else
+				{
+					$given_answer = trim($smcFunc['htmlspecialchars'](strtolower($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q])));
+					if (!in_array($given_answer, $modSettings['question_id_cache']['questions'][$q]['answers']))
+						$incorrectQuestions[] = $q;
+				}
 			}
-			$smcFunc['db_free_result']($request);
 
 			if (!empty($incorrectQuestions))
 				$verification_errors[] = 'wrong_verification_answer';
@@ -2114,13 +2127,27 @@ function create_control_verification(&$verificationOptions, $do_test = false)
 		// Getting some new questions?
 		if ($thisVerification['number_questions'])
 		{
-			// Pick some random IDs
+			// Attempt to try the current page's language, followed by the user's preference, followed by the site default.
+			$possible_langs = array();
+			if (isset($_SESSION['language']))
+				$possible_langs[] = strtr($_SESSION['language'], array('-utf8' => ''));
+			if (!empty($user_info['language']));
+			$possible_langs[] = $user_info['language'];
+			$possible_langs[] = $language;
+
 			$questionIDs = array();
-			if ($thisVerification['number_questions'] == 1)
-				$questionIDs[] = $modSettings['question_id_cache'][array_rand($modSettings['question_id_cache'], $thisVerification['number_questions'])];
-			else
-				foreach (array_rand($modSettings['question_id_cache'], $thisVerification['number_questions']) as $index)
-					$questionIDs[] = $modSettings['question_id_cache'][$index];
+			foreach ($possible_langs as $lang)
+			{
+				$lang = strtr($lang, array('-utf8' => ''));
+				if (isset($modSettings['question_id_cache']['langs'][$lang]))
+				{
+					// If we find questions for this, grab the ids from this language's ones, randomize the array and take just the number we need.
+					$questionIDs = $modSettings['question_id_cache']['langs'][$lang];
+					shuffle($questionIDs);
+					$questionIDs = array_slice($questionIDs, 0, $thisVerification['number_questions']);
+					break;
+				}
+			}
 		}
 	}
 	else
@@ -2141,29 +2168,20 @@ function create_control_verification(&$verificationOptions, $do_test = false)
 	// Have we got some questions to load?
 	if (!empty($questionIDs))
 	{
-		$request = $smcFunc['db_query']('', '
-			SELECT id_comment, body AS question
-			FROM {db_prefix}log_comments
-			WHERE comment_type = {string:ver_test}
-				AND id_comment IN ({array_int:comment_ids})',
-			array(
-				'ver_test' => 'ver_test',
-				'comment_ids' => $questionIDs,
-			)
-		);
 		$_SESSION[$verificationOptions['id'] . '_vv']['q'] = array();
-		while ($row = $smcFunc['db_fetch_assoc']($request))
+		foreach ($questionIDs as $q)
 		{
+			// Bit of a shortcut this.
+			$row = &$modSettings['question_id_cache']['questions'][$q];
 			$thisVerification['questions'][] = array(
-				'id' => $row['id_comment'],
+				'id' => $q,
 				'q' => parse_bbc($row['question']),
-				'is_error' => !empty($incorrectQuestions) && in_array($row['id_comment'], $incorrectQuestions),
+				'is_error' => !empty($incorrectQuestions) && in_array($q, $incorrectQuestions),
 				// Remember a previous submission?
-				'a' => isset($_REQUEST[$verificationOptions['id'] . '_vv'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) : '',
+				'a' => isset($_REQUEST[$verificationOptions['id'] . '_vv'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) : '',
 			);
-			$_SESSION[$verificationOptions['id'] . '_vv']['q'][] = $row['id_comment'];
+			$_SESSION[$verificationOptions['id'] . '_vv']['q'][] = $q;
 		}
-		$smcFunc['db_free_result']($request);
 	}
 
 	$_SESSION[$verificationOptions['id'] . '_vv']['count'] = empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) ? 1 : $_SESSION[$verificationOptions['id'] . '_vv']['count'] + 1;
@@ -2230,11 +2248,12 @@ function AutoSuggest_Search_Member()
 	$request = $smcFunc['db_query']('', '
 		SELECT id_member, real_name
 		FROM {db_prefix}members
-		WHERE real_name LIKE {string:search}' . (!empty($context['search_param']['buddies']) ? '
+		WHERE {raw:real_name} LIKE {string:search}' . (!empty($context['search_param']['buddies']) ? '
 			AND id_member IN ({array_int:buddy_list})' : '') . '
 			AND is_activated IN (1, 11)
 		LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'),
 		array(
+			'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
 			'buddy_list' => $user_info['buddies'],
 			'search' => $_REQUEST['search'],
 		)
@@ -2278,12 +2297,13 @@ function AutoSuggest_Search_MemberGroups()
 	$request = $smcFunc['db_query']('', '
 		SELECT id_group, group_name
 		FROM {db_prefix}membergroups
-		WHERE group_name LIKE {string:search}
+		WHERE {raw:group_name} LIKE {string:search}
 			AND min_posts = {int:min_posts}
 			AND id_group NOT IN ({array_int:invalid_groups})
 			AND hidden != {int:hidden}
 		',
 		array(
+			'group_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(group_name}' : 'group_name',
 			'min_posts' => -1,
 			'invalid_groups' => array(1,3),
 			'hidden' => 2,

+ 8 - 2
Sources/Subs-Members.php

@@ -730,6 +730,9 @@ function registerMember(&$regOptions, $return_errors = false)
 	);
 	$memberID = $smcFunc['db_insert_id']('{db_prefix}members', 'id_member');
 
+	// Call an optional function as notification of registration.
+	call_integration_hook('integrate_post_register', array(&$regOptions, &$theme_vars, &$memberID));
+
 	// Update the number of members and latest member's info - and pass the name, but remove the 's.
 	if ($regOptions['register_vars']['is_activated'] == 1)
 		updateStats('member', $memberID, $regOptions['register_vars']['real_name']);
@@ -928,9 +931,11 @@ function isReservedName($name, $current_ID_MEMBER = 0, $is_name = true, $fatal =
 		SELECT id_member
 		FROM {db_prefix}members
 		WHERE ' . (empty($current_ID_MEMBER) ? '' : 'id_member != {int:current_member}
-			AND ') . '(real_name LIKE {string:check_name} OR member_name LIKE {string:check_name})
+			AND ') . '({raw:real_name} LIKE {string:check_name} OR {raw:member_name} LIKE {string:check_name})
 		LIMIT 1',
 		array(
+			'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
+			'member_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name',
 			'current_member' => $current_ID_MEMBER,
 			'check_name' => $checkName,
 		)
@@ -945,9 +950,10 @@ function isReservedName($name, $current_ID_MEMBER = 0, $is_name = true, $fatal =
 	$request = $smcFunc['db_query']('', '
 		SELECT id_group
 		FROM {db_prefix}membergroups
-		WHERE group_name LIKE {string:check_name}
+		WHERE {raw:group_name} LIKE {string:check_name}
 		LIMIT 1',
 		array(
+			'group_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(group_name)' : 'group_name',
 			'check_name' => $checkName,
 		)
 	);

+ 21 - 14
Sources/Subs-Menu.php

@@ -26,9 +26,6 @@ function createMenu($menuData, $menuOptions = array())
 {
 	global $context, $settings, $options, $txt, $modSettings, $scripturl, $smcFunc, $user_info, $sourcedir, $options;
 
-	// Work out where we should get our images from.
-	$context['menu_image_path'] = file_exists($settings['theme_dir'] . '/images/admin/change_menu.png') ? $settings['images_url'] . '/admin' : $settings['default_images_url'] . '/admin';
-
 	/* Note menuData is array of form:
 
 		Possible fields:
@@ -127,10 +124,29 @@ function createMenu($menuData, $menuOptions = array())
 						if (!isset($area['force_menu_into_arms_of_another_menu']) && $user_info['name'] == 'iamanoompaloompa')
 							$menu_context['sections'][$section_id]['areas'][$area_id] = unserialize(base64_decode('YTozOntzOjU6ImxhYmVsIjtzOjEyOiJPb21wYSBMb29tcGEiO3M6MzoidXJsIjtzOjQzOiJodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL09vbXBhX0xvb21wYXM/IjtzOjQ6Imljb24iO3M6ODY6IjxpbWcgc3JjPSJodHRwOi8vd3d3LnNpbXBsZW1hY2hpbmVzLm9yZy9pbWFnZXMvb29tcGEuZ2lmIiBhbHQ9IkknbSBhbiBPb21wYSBMb29tcGEiIC8+Ijt9'));
 						elseif (isset($area['icon']))
-							$menu_context['sections'][$section_id]['areas'][$area_id]['icon'] = '<img src="' . $context['menu_image_path'] . '/' . $area['icon'] . '" alt="" />&nbsp;&nbsp;';
+							$menu_context['sections'][$section_id]['areas'][$area_id]['icon'] = file_exists($settings['theme_dir'] . '/images/admin/' . $area['icon']) ? '<img src="' . $settings['images_url'] . '/admin/' . $area['icon'] . '" alt="" />&nbsp;&nbsp;' : '<img src="' . $settings['default_images_url'] . '/admin/' . $area['icon'] . '" alt="" />&nbsp;&nbsp;';
 						else
 							$menu_context['sections'][$section_id]['areas'][$area_id]['icon'] = '';
 
+						// Mod authors may wish to just set such an icon. Easy here, just drop in a URL.
+						if (!empty($menuOptions['do_big_icons']))
+						{
+							if (isset($area['bigicon']))
+								$menu_context['sections'][$section_id]['areas'][$area_id]['bigicon'] = $area['bigicon'];
+							// Otherwise we try to use the big icon, which has the same filename as the small one but in another folder.
+							elseif (isset($area['icon']))
+							{
+								if (file_exists($settings['theme_dir'] . '/images/admin/big/' . $area['icon']))
+									$menu_context['sections'][$section_id]['areas'][$area_id]['bigicon'] = $settings['images_url'] . '/admin/big/' . $area['icon'];
+								elseif (file_exists($settings['default_theme_dir'] . '/images/admin/big/' . $area['icon']))
+									$menu_context['sections'][$section_id]['areas'][$area_id]['bigicon'] = $settings['default_images_url'] . '/admin/big/' . $area['icon'];
+							}
+
+							// They do need an icon. Have they got one?
+							if (empty($menu_context['sections'][$section_id]['areas'][$area_id]['bigicon']))
+								$menu_context['sections'][$section_id]['areas'][$area_id]['bigicon'] = $settings['default_images_url'] . '/admin/big/default.png';
+						}
+
 						// Did it have subsections?
 						if (!empty($area['subsections']))
 						{
@@ -245,20 +261,11 @@ function createMenu($menuData, $menuOptions = array())
 		return false;
 	}
 
-	// What type of menu is this?
-	if (empty($menuOptions['menu_type']))
-	{
-		$menuOptions['menu_type'] = '_' . (empty($options['use_sidebar_menu']) ? 'dropdown' : 'sidebar');
-		$menu_context['can_toggle_drop_down'] = !$user_info['is_guest'] && isset($settings['theme_version']) && $settings['theme_version'] >= 2.0;
-	}
-	else
-		$menu_context['can_toggle_drop_down'] = !empty($menuOptions['can_toggle_drop_down']);
-
 	// Almost there - load the template and add to the template layers.
 	if (!WIRELESS)
 	{
 		loadTemplate(isset($menuOptions['template_name']) ? $menuOptions['template_name'] : 'GenericMenu');
-		$menu_context['layer_name'] = (isset($menuOptions['layer_name']) ? $menuOptions['layer_name'] : 'generic_menu') . $menuOptions['menu_type'];
+		$menu_context['layer_name'] = (isset($menuOptions['layer_name']) ? $menuOptions['layer_name'] : 'generic_menu') . '_dropdown';
 		$context['template_layers'][] = $menu_context['layer_name'];
 	}
 

+ 2 - 2
Sources/Subs-Post.php

@@ -360,7 +360,7 @@ function fixTags(&$message)
 		fixTag($message, $param['tag'], $param['protocols'], $param['embeddedUrl'], $param['hasEqualSign'], !empty($param['hasExtra']));
 
 	// Now fix possible security problems with images loading links automatically...
-	$message = preg_replace_callback('~(\[img.*?\])(.+?)\[/img\]~is', create_function('$m', '"$m[1]" . preg_replace("~action(=|%3d)(?!dlattach)~i", "action-", "$2") . "[/img]";'), $message);
+	$message = preg_replace_callback('~(\[img.*?\])(.+?)\[/img\]~is', create_function('$m', 'return "$m[1]" . preg_replace("~action(=|%3d)(?!dlattach)~i", "action-", "$m[2]") . "[/img]";'), $message);
 
 	// Limit the size of images posted?
 	if (!empty($modSettings['max_image_width']) || !empty($modSettings['max_image_height']))
@@ -2971,4 +2971,4 @@ function user_info_callback($matches)
 	return $use_ref ? $ref : $matches[0];
 }
 
-?>
+?>

+ 4 - 7
Sources/Subs.php

@@ -416,9 +416,8 @@ function updateMemberData($members, $data)
  *
  * @param array $changeArray
  * @param bool $update = false
- * @param bool $debug = false
  */
-function updateSettings($changeArray, $update = false, $debug = false)
+function updateSettings($changeArray, $update = false)
 {
 	global $modSettings, $smcFunc;
 
@@ -2590,9 +2589,9 @@ function redirectexit($setLocation = '', $refresh = false)
 	if (!empty($modSettings['queryless_urls']) && (empty($context['server']['is_cgi']) || ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1) && (!empty($context['server']['is_apache']) || !empty($context['server']['is_lighttpd']) || !empty($context['server']['is_litespeed'])))
 	{
 		if (defined('SID') && SID != '')
-			$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html?' . SID . '\$2'", $setLocation);
+			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~', create_function('$m', 'global $scripturl; return $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html?\' . SID. (isset($m[2]) ? "$m[2]" : "");'), $setLocation);
 		else
-			$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2'", $setLocation);
+			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~', create_function('$m', 'global $scripturl; return $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html\' . (isset($m[2]) ? "$m[2]" : "");'), $setLocation);
 	}
 
 	// Maybe integrations want to change where we are heading?
@@ -2885,10 +2884,8 @@ function setupThemeContext($forceload = false)
 		$_SESSION['unread_messages'] = $user_info['unread_messages'];
 
 		if (allowedTo('moderate_forum'))
-		{
 			$context['unapproved_members'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
-			$context['unapproved_members_text'] = $context['unapproved_members'] == 1 ? sprintf($txt['approve_one_member_waiting'],  $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve') : sprintf($txt['approve_many_members_waiting'],  $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve', $context['unapproved_members']);
-		}
+
 		$context['show_open_reports'] = empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1;
 
 		$context['user']['avatar'] = array();

+ 1 - 1
Sources/Subscriptions-PayPal.php

@@ -261,7 +261,7 @@ class paypal_payment
 			$this->_findSubscription();
 
 		// Verify the currency!
-		if (strtolower($_POST['mc_currency']) !== $modSettings['paid_currency_code'])
+		if (strtolower($_POST['mc_currency']) !== strtolower($modSettings['paid_currency_code']))
 			exit;
 
 		// Can't exist if it doesn't contain anything.

+ 2 - 1
Sources/Themes.php

@@ -957,6 +957,7 @@ function PickTheme()
 		'url' => $scripturl . '?action=theme;sa=pick;u=' . (!empty($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0),
 		'name' => $txt['theme_pick'],
 	);
+	$context['default_theme_id'] = $modSettings['theme_default'];
 
 	$_SESSION['id_theme'] = 0;
 
@@ -1224,7 +1225,7 @@ function PickTheme()
 		$context['available_themes'][$id_theme]['description'] = $txt['theme_description'];
 
 		// Are there any variants?
-		if (file_exists($theme_data['theme_dir'] . '/index.template.php') && empty($theme_data['disable_user_variant']))
+		if (file_exists($theme_data['theme_dir'] . '/index.template.php') && (empty($theme_data['disable_user_variant']) || allowedTo('admin_forum')))
 		{
 			$file_contents = implode('', file($theme_data['theme_dir'] . '/index.template.php'));
 			if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches))

+ 82 - 64
Themes/default/Admin.template.php

@@ -10,6 +10,28 @@
  * @version 2.1 Alpha 1
  */
 
+function template_maint_warning_above()
+{
+	global $txt, $context, $scripturl;
+
+	echo '
+	<div class="errorbox" id="errors">
+		<dl>
+			<dt>
+				<strong id="error_serious">', $txt['forum_in_maintainence'], '</strong>
+			</dt>
+			<dd class="error" id="error_list">
+				', sprintf($txt['maintenance_page'], $scripturl . '?action=admin;area=serversettings;' . $context['session_var'] . '=' . $context['session_id']), '
+			</dd>
+		</dl>
+	</div>';
+}
+
+function template_maint_warning_below()
+{
+
+}
+
 /**
  * This is the administration center home.
  */
@@ -78,23 +100,30 @@ function template_admin()
 			</div>
 		</div>';
 
-	echo '
-		<div class="windowbg2 quick_tasks">
-			<div class="content">
-				<ul id="quick_tasks" class="flow_hidden">';
+	$use_bg2 = true;
+	foreach ($context[$context['admin_menu_name']]['sections'] as $area_id => $area)
+	{
+		echo '
+		<fieldset id="group_', $area_id, '" class="', $use_bg2 ? 'windowbg2' : 'windowbg', ' admin_group">
+			<legend>', $area['title'], '</legend>';
+
+		foreach ($area['areas'] as $item_id => $item)
+		{
+			// No point showing the 'home' page here, we're already on it!
+			if ($area_id == 'forum' && $item_id == 'index')
+				continue;
+
+			$url = isset($item['url']) ? $item['url'] : $scripturl . '?action=admin;area=' . $item_id . (!empty($context[$context['admin_menu_name']]['extra_parameters']) ? $context[$context['admin_menu_name']]['extra_parameters'] : '');
+			echo '
+				<a href="', $url, '"><img src="', $item['bigicon'], '" alt="" /><br />', $item['label'], '</a>';
+		}
 
-	foreach ($context['quick_admin_tasks'] as $task)
 		echo '
-					<li>
-						', !empty($task['icon']) ? '<a href="' . $task['href'] . '"><img src="' . $settings['default_images_url'] . '/admin/' . $task['icon'] . '" alt="" class="home_image" /></a>' : '', '
-						<h5>', $task['link'], '</h5>
-						<span class="task">', $task['description'],'</span>
-					</li>';
+		</fieldset>';
+		$use_bg2 = !$use_bg2;
+	}
 
 	echo '
-				</ul>
-			</div>
-		</div>
 	</div>';
 
 	// The below functions include all the scripts needed from the simplemachines.org site. The language and format are passed for internationalization.
@@ -214,19 +243,6 @@ function template_credits()
 				</div>
 			</div>';
 
-	// Display latest support questions from simplemachines.org.
-	echo '
-			<div class="cat_bar">
-				<h3 class="catbg">
-					<a href="', $scripturl, '?action=helpadmin;help=latest_support" onclick="return reqOverlayDiv(this.href);" class="help"><img src="', $settings['images_url'], '/helptopics_hd.png" class="icon" alt="', $txt['help'], '" /></a> ', $txt['support_latest'], '
-				</h3>
-			</div>
-			<div class="windowbg">
-				<div class="content">
-					<div id="latestSupport">', $txt['support_latest_fetch'], '</div>
-				</div>
-			</div>';
-
 	// The most important part - the credits :P.
 	echo '
 			<div id="credits_sections" class="cat_bar">
@@ -289,8 +305,7 @@ function template_credits()
 	echo '
 		// ]]></script>
 		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=current-version.js"></script>
-		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=latest-news.js"></script>
-		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=latest-support.js"></script>';
+		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=latest-news.js"></script>';
 
 	// This sets the latest support stuff.
 	echo '
@@ -737,8 +752,8 @@ function template_show_settings()
 			if ($config_var['type'] == 'title')
 			{
 				echo '
-					<div class="cat_bar">
-						<h3 class="', !empty($config_var['class']) ? $config_var['class'] : 'catbg', '"', !empty($config_var['force_div_id']) ? ' id="' . $config_var['force_div_id'] . '"' : '', '>
+					<div class="title_bar">
+						<h3 class="', !empty($config_var['class']) ? $config_var['class'] : 'titlebg', '"', !empty($config_var['force_div_id']) ? ' id="' . $config_var['force_div_id'] . '"' : '', '>
 							', ($config_var['help'] ? '<a href="' . $scripturl . '?action=helpadmin;help=' . $config_var['help'] . '" onclick="return reqOverlayDiv(this.href);" class="help"><img src="' . $settings['images_url'] . '/helptopics_hd.png" class="icon" alt="' . $txt['help'] . '" /></a>' : ''), '
 							', $config_var['label'], '
 						</h3>
@@ -1398,45 +1413,48 @@ function template_callback_question_answer_list()
 {
 	global $txt, $context, $settings;
 
-	echo '
-			<dt>
-				<strong>', $txt['setup_verification_question'], '</strong>
-			</dt>
-			<dd>
-				<strong>', $txt['setup_verification_answer'], '</strong>
-			</dd>';
+	foreach ($context['languages'] as $lang_id => $lang)
+	{
+		$lang_id = strtr($lang_id, array('-utf8' => ''));
+		$lang['name'] = strtr($lang['name'], array('-utf8' => ''));
 
-	foreach ($context['question_answers'] as $data)
 		echo '
+						<dt id="qa_dt_', $lang_id, '" class="qa_link">
+							<a href="javascript:void(0);">[ ', $lang['name'], ' ]</a>
+						</dt>
+						<fieldset id="qa_fs_', $lang_id, '" class="qa_fieldset">
+							<legend><a href="javascript:void(0);">', $lang['name'], '</a></legend>
+							<dl class="settings">
+								<dt>
+									<strong>', $txt['setup_verification_question'], '</strong>
+								</dt>
+								<dd>
+									<strong>', $txt['setup_verification_answer'], '</strong>
+								</dd>';
+
+		if (!empty($context['qa_by_lang'][$lang_id]))
+			foreach ($context['qa_by_lang'][$lang_id] as $q_id)
+			{
+				$question = $context['question_answers'][$q_id];
+				echo '
+								<dt>
+									<input type="text" name="question[', $lang_id, '][', $q_id, ']" value="', $question['question'], '" size="50" class="input_text verification_question" />
+								</dt>
+								<dd>';
+				foreach ($question['answers'] as $answer)
+					echo '
+									<input type="text" name="answer[', $lang_id, '][', $q_id, '][]" value="', $answer, '" size="50" class="input_text verification_answer" />';
 
-			<dt>
-				<input type="text" name="question[', $data['id'], ']" value="', $data['question'], '" size="50" class="input_text verification_question" />
-			</dt>
-			<dd>
-				<input type="text" name="answer[', $data['id'], ']" value="', $data['answer'], '" size="50" class="input_text verification_answer" />
-			</dd>';
+				echo '
+									<div class="qa_add_answer"><a href="javascript:void(0);" onclick="return addAnswer(this);">[ ', $txt['setup_verification_add_answer'], ' ]</a></div>
+								</dd>';
+			}
 
-	// Some blank ones.
-	for ($count = 0; $count < 3; $count++)
 		echo '
-			<dt>
-				<input type="text" name="question[]" size="50" class="input_text verification_question" />
-			</dt>
-			<dd>
-				<input type="text" name="answer[]" size="50" class="input_text verification_answer" />
-			</dd>';
-
-	echo '
-		<dt id="add_more_question_placeholder" style="display: none;"></dt><dd></dd>
-		<dt id="add_more_link_div" style="display: none;">
-			<a href="#" onclick="addAnotherQuestion(); return false;">&#171; ', $txt['setup_verification_add_more'], ' &#187;</a>
-		</dt><dd></dd>';
-
-	// The javascript needs to go at the end but we'll put it in this template for looks.
-	$context['settings_post_javascript'] .= '
-		var placeHolder = document.getElementById(\'add_more_question_placeholder\');
-		document.getElementById(\'add_more_link_div\').style.display = \'\';
-	';
+								<dt class="qa_add_question"><a href="javascript:void(0);">[ ', $txt['setup_verification_add_more'], ' ]</a></dt>
+							</dl>
+						</fieldset>';
+	}
 }
 
 // Repairing boards.

+ 2 - 2
Themes/default/Errors.template.php

@@ -49,8 +49,8 @@ function template_error_log()
 		<form class="generic_list_wrapper" action="', $scripturl, '?action=admin;area=logs;sa=errorlog', $context['sort_direction'] == 'down' ? ';desc' : '', ';start=', $context['start'], $context['has_filter'] ? $context['filter']['href'] : '', '" method="post" accept-charset="', $context['character_set'], '">';
 
 	echo '
-			<div class="title_bar clear_right">
-				<h3 class="titlebg">
+			<div class="cat_bar clear_right">
+				<h3 class="catbg">
 					<a href="', $scripturl, '?action=helpadmin;help=error_log" onclick="return reqOverlayDiv(this.href);" class="help"><img src="', $settings['images_url'], '/helptopics.png" class="icon" alt="', $txt['help'], '" /></a> ', $txt['errlog'], '
 				</h3>
 			</div>

+ 0 - 103
Themes/default/GenericMenu.template.php

@@ -10,109 +10,6 @@
  * @version 2.1 Alpha 1
  */
 
-// This contains the html for the side bar of the admin center, which is used for all admin pages.
-function template_generic_menu_sidebar_above()
-{
-	global $context, $settings, $options, $scripturl, $txt, $modSettings;
-
-	// This is the main table - we need it so we can keep the content to the right of it.
-	echo '
-	<div id="main_container">
-		<div id="left_admsection">';
-
-	// What one are we rendering?
-	$context['cur_menu_id'] = isset($context['cur_menu_id']) ? $context['cur_menu_id'] + 1 : 1;
-	$menu_context = &$context['menu_data_' . $context['cur_menu_id']];
-
-	// For every section that appears on the sidebar...
-	$firstSection = true;
-	foreach ($menu_context['sections'] as $section)
-	{
-		// Show the section header - and pump up the line spacing for readability.
-		echo '
-			<div class="adm_section">
-				<div class="cat_bar">
-					<h4 class="catbg">
-						', $section['title'], '
-					</h4>
-				</div>
-				<ul class="dropmenu left_admmenu">';
-
-		// For every area of this section show a link to that area (bold if it's currently selected.)
-		foreach ($section['areas'] as $i => $area)
-		{
-			// Not supposed to be printed?
-			if (empty($area['label']))
-				continue;
-
-			echo '
-					<li ', !empty($area['subsections']) ?'class="subsections"':'', ' ', ($i == $menu_context['current_area']) ?'id="menu_current_area"':'', '>';
-
-			// Is this the current area, or just some area?
-			if ($i == $menu_context['current_area'])
-			{
-				echo '
-						<strong><a href="', isset($area['url']) ? $area['url'] : $menu_context['base_url'] . ';area=' . $i, $menu_context['extra_parameters'], '">', $area['label'], '</a></strong>';
-
-				if (empty($context['tabs']))
-					$context['tabs'] = isset($area['subsections']) ? $area['subsections'] : array();
-			}
-			else
-				echo '
-						<a href="', isset($area['url']) ? $area['url'] : $menu_context['base_url'] . ';area=' . $i, $menu_context['extra_parameters'], '">', $area['label'], '</a>';
-			// Is there any subsections?
-			if (!empty($area['subsections']))
-			{
-				echo '
-						<ul>';
-
-				foreach ($area['subsections'] as $sa => $sub)
-				{
-					if (!empty($sub['disabled']))
-						continue;
-
-					$url = isset($sub['url']) ? $sub['url'] : (isset($area['url']) ? $area['url'] : $menu_context['base_url'] . ';area=' . $i) . ';sa=' . $sa;
-
-					echo '
-							<li>
-								<a ', !empty($sub['selected']) ? 'class="chosen" ' : '', 'href="', $url, $menu_context['extra_parameters'], '">', $sub['label'], '</a>
-							</li>';
-				}
-
-				echo '
-						</ul>';
-			}
-			echo '
-					</li>';
-		}
-
-		echo '
-				</ul>
-			</div>';
-
-		$firstSection = false;
-	}
-
-	// This is where the actual "main content" area for the admin section starts.
-	echo '
-		</div>
-		<div id="main_admsection">';
-
-	// If there are any "tabs" setup, this is the place to shown them.
-	if (!empty($context['tabs']) && empty($context['force_disable_tabs']))
-		template_generic_menu_tabs($menu_context);
-}
-
-// Part of the sidebar layer - closes off the main bit.
-function template_generic_menu_sidebar_below()
-{
-	global $context, $settings, $options;
-
-	echo '
-		</div>
-	</div>';
-}
-
 // This contains the html for the side bar of the admin center, which is used for all admin pages.
 function template_generic_menu_dropdown_above()
 {

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

@@ -33,28 +33,6 @@ function template_avatar_settings_below()
 ';
 }
 
-function template_browse()
-{
-	global $context, $settings, $options, $scripturl, $txt;
-
-	echo '
-	<div id="manage_attachments">
-		<div class="cat_bar">
-			<h3 class="catbg">', $txt['attachment_manager_browse_files'], '</h3>
-		</div>
-		<div class="windowbg2">
-			<div class="content">
-				<a href="', $scripturl, '?action=admin;area=manageattachments;sa=browse">', $context['browse_type'] === 'attachments' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;" /> ' : '', $txt['attachment_manager_attachments'], '</a> |
-				<a href="', $scripturl, '?action=admin;area=manageattachments;sa=browse;avatars">', $context['browse_type'] === 'avatars' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;" /> ' : '', $txt['attachment_manager_avatars'], '</a> |
-				<a href="', $scripturl, '?action=admin;area=manageattachments;sa=browse;thumbs">', $context['browse_type'] === 'thumbs' ? '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;" /> ' : '', $txt['attachment_manager_thumbs'], '</a>
-			</div>
-		</div>
-	</div>';
-
-	template_show_list('file_list');
-
-}
-
 function template_maintenance()
 {
 	global $context, $settings, $options, $scripturl, $txt, $modSettings;

+ 3 - 4
Themes/default/ManageNews.template.php

@@ -261,8 +261,7 @@ function template_email_members_compose()
 			</div>
 			<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
 			<input type="hidden" name="email_force" value="', $context['email_force'], '" />
-			<input type="hidden" name="total_emails" value="', $context['total_emails'], '" />
-			<input type="hidden" name="max_id_member" value="', $context['max_id_member'], '" />';
+			<input type="hidden" name="total_emails" value="', $context['total_emails'], '" />';
 
 	foreach ($context['recipients'] as $key => $values)
 		echo '
@@ -420,8 +419,8 @@ function template_email_members_send()
 					<input type="hidden" name="subject" value="', $context['subject'], '" />
 					<input type="hidden" name="message" value="', $context['message'], '" />
 					<input type="hidden" name="start" value="', $context['start'], '" />
+					<input type="hidden" name="total_members" value="', $context['total_members'], '" />
 					<input type="hidden" name="total_emails" value="', $context['total_emails'], '" />
-					<input type="hidden" name="max_id_member" value="', $context['max_id_member'], '" />
 					<input type="hidden" name="send_pm" value="', $context['send_pm'], '" />
 					<input type="hidden" name="send_html" value="', $context['send_html'], '" />
 					<input type="hidden" name="parse_html" value="', $context['parse_html'], '" />';
@@ -456,4 +455,4 @@ function template_email_members_send()
 	// ]]></script>';
 }
 
-?>
+?>

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

@@ -491,7 +491,7 @@ function template_modify_group()
 
 	echo '
 	<div id="admincenter">
-		<form id="admin_form_wrapper" action="', $scripturl, '?action=admin;area=permissions;sa=modify2;group=', $context['group']['id'], ';pid=', $context['profile']['id'], '" method="post" accept-charset="', $context['character_set'], '" name="permissionForm" " onsubmit="return warnAboutDeny();">';
+		<form id="admin_form_wrapper" action="', $scripturl, '?action=admin;area=permissions;sa=modify2;group=', $context['group']['id'], ';pid=', $context['profile']['id'], '" method="post" accept-charset="', $context['character_set'], '" name="permissionForm" onsubmit="return warnAboutDeny();">';
 
 	if (!empty($modSettings['permission_enable_deny']) && $context['group']['id'] != -1)
 		echo '

+ 26 - 1
Themes/default/ManageScheduledTasks.template.php

@@ -17,10 +17,35 @@ function template_view_scheduled_tasks()
 
 	// We completed some tasks?
 	if (!empty($context['tasks_were_run']))
-		echo '
+	{
+		if (empty($context['scheduled_errors']))
+			echo '
 	<div id="task_completed">
 		', $txt['scheduled_tasks_were_run'], '
 	</div>';
+		else
+		{
+			echo '
+	<div class="errorbox" id="errors">
+			<dl>
+				<dt>
+					<strong id="error_serious">', $txt['scheduled_tasks_were_run_errors'], '</strong>
+				</dt>';
+
+			foreach ($context['scheduled_errors'] as $task => $errors)
+			{
+				echo '
+				<dd class="error">
+					<strong>', isset($txt['scheduled_task_' . $task]) ? $txt['scheduled_task_' . $task] : $task, '</strong>
+					<ul><li>', implode('</li><li>', $errors), '</li></ul>
+				</dd>';
+			}
+
+			echo '
+			</dl>
+		</div>';
+		}
+	}
 
 	template_show_list('scheduled_tasks');
 }

+ 47 - 62
Themes/default/ModerationCenter.template.php

@@ -14,6 +14,9 @@ function template_moderation_center()
 {
 	global $settings, $options, $context, $txt, $scripturl;
 
+	// Show moderators notes.
+	template_notes();
+
 	// Show a welcome message to the user.
 	echo '
 	<div id="modcenter">';
@@ -39,49 +42,6 @@ function template_moderation_center()
 	<br class="clear" />';
 }
 
-function template_latest_news()
-{
-	global $settings, $options, $context, $txt, $scripturl;
-
-	echo '
-		<div class="cat_bar">
-			<h3 class="catbg">
-				<a href="', $scripturl, '?action=helpadmin;help=live_news" onclick="return reqOverlayDiv(this.href);" class="help"><img src="', $settings['images_url'], '/helptopics_hd.png" alt="', $txt['help'], '" class="icon" /></a> ', $txt['mc_latest_news'], '
-			</h3>
-		</div>
-		<div class="windowbg">
-			<div class="content">
-				<div id="smfAnnouncements" class="smalltext">', $txt['mc_cannot_connect_sm'], '</div>
-			</div>
-		</div>';
-
-	// This requires a lot of javascript...
-	// @todo Put this in it's own file!!
-	echo '
-		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=current-version.js"></script>
-		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=latest-news.js"></script>
-		<script type="text/javascript"><!-- // --><![CDATA[
-			var oAdminIndex = new smf_AdminIndex({
-				sSelf: \'oAdminCenter\',
-
-				bLoadAnnouncements: true,
-				sAnnouncementTemplate: ', JavaScriptEscape('
-					<dl>
-						%content%
-					</dl>
-				'), ',
-				sAnnouncementMessageTemplate: ', JavaScriptEscape('
-					<dt><a href="%href%">%subject%</a> ' . $txt['on'] . ' %time%</dt>
-					<dd>
-						%message%
-					</dd>
-				'), ',
-				sAnnouncementContainerId: \'smfAnnouncements\'
-			});
-		// ]]></script>';
-
-}
-
 // Show all the group requests the user can see.
 function template_group_requests_block()
 {
@@ -189,45 +149,56 @@ function template_notes()
 	global $settings, $options, $context, $txt, $scripturl;
 
 	echo '
-		<form action="', $scripturl, '?action=moderate;area=index" method="post">
-			<div class="cat_bar">
-				<h3 class="catbg">', $txt['mc_notes'], '</h3>
-			</div>
-			<div class="windowbg">
-				<div class="content modbox">';
+		<div class="modnotes">
+			<form action="', $scripturl, '?action=moderate;area=index;modnote" method="post">
+				<div class="cat_bar">
+					<h3 class="catbg">', $txt['mc_notes'], '</h3>
+				</div>
+				<div class="windowbg">
+					<div class="content modbox">';
 
 		if (!empty($context['notes']))
 		{
 			echo '
-					<ul class="reset moderation_notes">';
+						<ul class="reset moderation_notes">';
 
 			// Cycle through the notes.
 			foreach ($context['notes'] as $note)
 				echo '
-						<li class="smalltext"><a href="', $note['delete_href'], '"><img src="', $settings['images_url'], '/pm_recipient_delete.png" alt="" /></a> <strong>', $note['author']['link'], ':</strong> ', $note['text'], '</li>';
+							<li class="smalltext"><a href="', $note['delete_href'], '"><img src="', $settings['images_url'], '/pm_recipient_delete.png" alt="" /></a>', $note['time'] ,' <strong>', $note['author']['link'], ':</strong> ', $note['text'], '</li>';
 
 			echo '
-					</ul>
-					<div class="pagesection notes">
-						<span class="smalltext">', $context['page_index'], '</span>
-					</div>';
+						</ul>
+						<div class="pagesection notes">
+							<span class="smalltext">', $context['page_index'], '</span>
+						</div>';
 		}
 
 		echo '
-					<div class="floatleft post_note">
+						<div class="floatleft post_note">
 						<input type="text" name="new_note" value="', $txt['mc_click_add_note'], '" style="width: 95%;" onclick="if (this.value == \'', $txt['mc_click_add_note'], '\') this.value = \'\';" class="input_text" />
+						</div>
+						<input type="submit" name="makenote" value="', $txt['mc_add_note'], '" class="button_submit" />
 					</div>
-					<input type="submit" name="makenote" value="', $txt['mc_add_note'], '" class="button_submit" />
 				</div>
-			</div>
-			<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
-		</form>';
+				<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
+			</form>
+		</div>';
 }
 
 function template_reported_posts()
 {
 	global $settings, $options, $context, $txt, $scripturl;
 
+	// Let them know the action was a success.
+	if (!empty($context['report_post_action']) && !empty($txt['report_action_'. $context['report_post_action']]))
+	{
+		echo '
+			<div class="infobox">
+				', $txt['report_action_'. $context['report_post_action']], '
+			</div>';
+	}
+
 	echo '
 	<form id="reported_posts" action="', $scripturl, '?action=moderate;area=reports', $context['view_closed'] ? ';sa=closed' : '', ';start=', $context['start'], '" method="post" accept-charset="', $context['character_set'], '">
 		<div class="cat_bar">
@@ -244,6 +215,8 @@ function template_reported_posts()
 	$details_button = create_button('details.png', 'mc_reportedp_details', 'mc_reportedp_details', 'class="centericon"');
 	$ignore_button = create_button('ignore.png', 'mc_reportedp_ignore', 'mc_reportedp_ignore', 'class="centericon"');
 	$unignore_button = create_button('ignore.png', 'mc_reportedp_unignore', 'mc_reportedp_unignore', 'class="centericon"');
+	$ban_button = create_button('close.png', 'mc_reportedp_ban', 'mc_reportedp_ban', 'class="centericon"');
+	$delete_button = create_button('delete.png', 'mc_reportedp_delete', 'mc_reportedp_delete', 'class="centericon"');
 
 	foreach ($context['reports'] as $report)
 	{
@@ -251,7 +224,7 @@ function template_reported_posts()
 		<div class="generic_list_wrapper ', $report['alternate'] ? 'windowbg' : 'windowbg2', '">
 			<div class="content">
 				<h5>
-					<strong><a href="', $report['topic_href'], '">', $report['subject'], '</a></strong> ', $txt['mc_reportedp_by'], ' <strong>', $report['author']['link'], '</strong>
+					<strong>', !empty($report['topic']['board_name']) ? '<a href="' . $scripturl . '?board=' . $report['topic']['id_board'] . '.0">' . $report['topic']['board_name'] . '</a>' : '??', ' / <a href="', $report['topic']['href'], '">', $report['subject'], '</a></strong> ', $txt['mc_reportedp_by'], ' <strong>', $report['author']['link'], '</strong>
 				</h5>
 				<div class="smalltext">
 					', $txt['mc_reportedp_last_reported'], ': ', $report['last_updated'], '&nbsp;-&nbsp;';
@@ -270,7 +243,19 @@ function template_reported_posts()
 				<ul class="quickbuttons">
 					<li><a href="', $report['report_href'], '">', $details_button, '</a></li>
 					<li><a href="', $scripturl, '?action=moderate;area=reports', $context['view_closed'] ? ';sa=closed' : '', ';ignore=', (int) !$report['ignore'], ';rid=', $report['id'], ';start=', $context['start'], ';', $context['session_var'], '=', $context['session_id'], '" ', !$report['ignore'] ? 'onclick="return confirm(\'' . $txt['mc_reportedp_ignore_confirm'] . '\');"' : '', '>', $report['ignore'] ? $unignore_button : $ignore_button, '</a></li>
-					<li><a href="', $scripturl, '?action=moderate;area=reports', $context['view_closed'] ? ';sa=closed' : '', ';close=', (int) !$report['closed'], ';rid=', $report['id'], ';start=', $context['start'], ';', $context['session_var'], '=', $context['session_id'], '">', $close_button, '</a></li>
+					<li><a href="', $scripturl, '?action=moderate;area=reports', $context['view_closed'] ? ';sa=closed' : '', ';close=', (int) !$report['closed'], ';rid=', $report['id'], ';start=', $context['start'], ';', $context['session_var'], '=', $context['session_id'], '">', $close_button, '</a></li>';
+
+		// Delete message button.
+		if (!$report['closed'] && (is_array($context['report_remove_any_boards']) && in_array($report['topic']['id_board'], $context['report_remove_any_boards'])))
+			echo '
+					<li><a href="', $scripturl, '?action=deletemsg;topic=', $report['topic']['id'] ,'.0;msg=', $report['topic']['id_msg'] ,';modcenter;', $context['session_var'], '=', $context['session_id'], '" onclick="return confirm(\'' , $txt['mc_reportedp_delete_confirm'] , '\');">', $delete_button, '</a></li>';
+
+		// Ban this user button.
+		if (!$report['closed'] && !empty($context['report_manage_bans']))
+			echo '
+					<li><a href="', $scripturl, '?action=admin;area=ban;sa=add', (!empty($report['author']['id']) ? ';u='. $report['author']['id'] : ';msg='. $report['topic']['id_msg']) ,';', $context['session_var'], '=', $context['session_id'], '">', $ban_button, '</a></li>';
+
+		echo '
 					<li>', !$context['view_closed'] ? '<input type="checkbox" name="close[]" value="' . $report['id'] . '" class="input_check" />' : '', '</li>
 				</ul>
 			</div>

+ 3 - 6
Themes/default/PersonalMessage.template.php

@@ -11,7 +11,7 @@
  */
 
 
-// This is the main sidebar for the personal messages section.
+// This is for stuff above the menu in the personal messages section.
 function template_pm_above()
 {
 	global $context, $settings, $options, $txt;
@@ -1108,9 +1108,6 @@ function template_send()
 
 	// Send, Preview, spellcheck buttons.
 	echo '
-				<p>
-					<label for="outbox"><input type="checkbox" name="outbox" id="outbox" value="1" tabindex="', $context['tabindex']++, '"', $context['copy_to_outbox'] ? ' checked="checked"' : '', ' class="input_check" /> ', $txt['pm_save_outbox'], '</label>
-				</p>
 				<hr class="hrcolor" />
 				<span id="post_confirm_strip" class="righttext">
 					', template_control_richedit_buttons($context['post_box_name']), '
@@ -1127,7 +1124,7 @@ function template_send()
 	</form>';
 
 	// If the admin enabled the pm drafts feature, show a draft selection box
-	if (!empty($modSettings['drafts_enabled']) && !empty($context['drafts_pm_save']) && !empty($context['drafts']) && !empty($options['drafts_show_saved_enabled']))
+	if (!empty($context['drafts_pm_save']) && !empty($context['drafts']) && !empty($options['drafts_show_saved_enabled']))
 	{
 		echo '
 			<br />
@@ -1180,7 +1177,7 @@ function template_send()
 					var x = new Array();
 					var textFields = [\'subject\', ', JavaScriptEscape($context['post_box_name']), ', \'to\', \'bcc\'];
 					var numericFields = [\'recipient_to[]\', \'recipient_bcc[]\'];
-					var checkboxFields = [\'outbox\'];
+					var checkboxFields = [];
 
 					for (var i = 0, n = textFields.length; i < n; i++)
 						if (textFields[i] in document.forms.postmodify)

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

@@ -490,7 +490,7 @@ function template_main()
 		echo '
 					</div>';
 	// If the admin enabled the drafts feature, show a draft selection box
-	if (!empty($modSettings['drafts_enabled']) && !empty($context['drafts']) && !empty($options['drafts_show_saved_enabled']))
+	if (!empty($modSettings['drafts_post_enabled']) && !empty($context['drafts']) && !empty($options['drafts_show_saved_enabled']))
 	{
 		echo '
 					<div id="postDraftOptionsHeader" class="title_bar">

+ 5 - 19
Themes/default/Profile.template.php

@@ -104,7 +104,7 @@ function template_summary()
 	echo '
 					<a href="', $scripturl, '?action=profile;area=showposts;u=', $context['id_member'], '">', $txt['showPosts'], '</a><br />';
 
-	if ($context['user']['is_owner'] && !empty($modSettings['drafts_enabled']))
+	if ($context['user']['is_owner'] && !empty($modSettings['drafts_post_enabled']))
 		echo '
 					<a href="', $scripturl, '?action=profile;area=showdrafts;u=', $context['id_member'], '">', $txt['drafts_show'], '</a><br />';
 
@@ -1431,7 +1431,7 @@ function template_profile_pm_settings()
 									<label for="pm_prefs">', $txt['pm_display_mode'], ':</label>
 								</dt>
 								<dd>
-									<select name="pm_prefs" id="pm_prefs" onchange="if (this.value == 2 &amp;&amp; !document.getElementById(\'copy_to_outbox\').checked) alert(\'', $txt['pm_recommend_enable_outbox'], '\');">
+									<select name="pm_prefs" id="pm_prefs">
 										<option value="0"', $context['display_mode'] == 0 ? ' selected="selected"' : '', '>', $txt['pm_display_mode_all'], '</option>
 										<option value="1"', $context['display_mode'] == 1 ? ' selected="selected"' : '', '>', $txt['pm_display_mode_one'], '</option>
 										<option value="2"', $context['display_mode'] == 2 ? ' selected="selected"' : '', '>', $txt['pm_display_mode_linked'], '</option>
@@ -1488,13 +1488,6 @@ function template_profile_pm_settings()
 						</dl>
 						<hr />
 						<dl>
-								<dt>
-										<label for="copy_to_outbox"> ', $txt['copy_to_outbox'], '</label>
-								</dt>
-								<dd>
-										<input type="hidden" name="default_options[copy_to_outbox]" value="0" />
-										<input type="checkbox" name="default_options[copy_to_outbox]" id="copy_to_outbox" value="1"', !empty($context['member']['options']['copy_to_outbox']) ? ' checked="checked"' : '', ' class="input_check" />
-								</dd>
 								<dt>
 										<label for="pm_remove_inbox_label">', $txt['pm_remove_inbox_label'], '</label>
 								</dt>
@@ -1525,13 +1518,6 @@ function template_profile_theme_settings()
 								<input type="hidden" name="default_options[show_children]" value="0" />
 								<input type="checkbox" name="default_options[show_children]" id="show_children" value="1"', !empty($context['member']['options']['show_children']) ? ' checked="checked"' : '', ' class="input_check" />
 							</dd>
-							<dt>
-								<label for="use_sidebar_menu">', $txt['use_sidebar_menu'], '</label>
-							</dt>
-							<dd>
-								<input type="hidden" name="default_options[use_sidebar_menu]" value="0" />
-								<input type="checkbox" name="default_options[use_sidebar_menu]" id="use_sidebar_menu" value="1"', !empty($context['member']['options']['use_sidebar_menu']) ? ' checked="checked"' : '', ' class="input_check" />
-							</dd>
 							<dt>
 								<label for="show_no_avatars">', $txt['show_no_avatars'], '</label>
 							</dt>
@@ -1547,7 +1533,7 @@ function template_profile_theme_settings()
 								<input type="checkbox" name="default_options[show_no_signatures]" id="show_no_signatures" value="1"', !empty($context['member']['options']['show_no_signatures']) ? ' checked="checked"' : '', ' class="input_check" />
 							</dd>';
 
-	if ($modSettings['allow_no_censored'])
+	if (!empty($modSettings['allow_no_censored']))
 		echo '
 							<dt>
 								<label for="show_no_censored">' . $txt['show_no_censored'] . '</label>
@@ -1645,7 +1631,7 @@ function template_profile_theme_settings()
 								</select>
 							</dd>';
 
-	if (!empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_autosave_enabled']))
+	if ((!empty($modSettings['drafts_post_enabled']) || !empty($modSettings['drafts_pm_enabled'])) && !empty($modSettings['drafts_autosave_enabled']))
 		echo '
 							<dt>
 								<label for="drafts_autosave_enabled">', $txt['drafts_autosave_enabled'], '</label>
@@ -1654,7 +1640,7 @@ function template_profile_theme_settings()
 								<input type="hidden" name="default_options[drafts_autosave_enabled]" value="0" />
 								<label for="drafts_autosave_enabled"><input type="checkbox" name="default_options[drafts_autosave_enabled]" id="drafts_autosave_enabled" value="1"', !empty($context['member']['options']['drafts_autosave_enabled']) ? ' checked="checked"' : '', ' class="input_check" /></label>
 							</dd>';
-	if (!empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_show_saved_enabled']))
+	if ((!empty($modSettings['drafts_post_enabled']) || !empty($modSettings['drafts_pm_enabled'])) && !empty($modSettings['drafts_show_saved_enabled']))
 		echo '
 							<dt>
 								<label for="drafts_show_saved_enabled">', $txt['drafts_show_saved_enabled'], '</label>

+ 0 - 10
Themes/default/Settings.template.php

@@ -25,11 +25,6 @@ function template_options()
 			'label' => $txt['show_children'],
 			'default' => true,
 		),
-		array(
-			'id' => 'use_sidebar_menu',
-			'label' => $txt['use_sidebar_menu'],
-			'default' => true,
-		),
 		array(
 			'id' => 'show_no_avatars',
 			'label' => $txt['show_no_avatars'],
@@ -75,11 +70,6 @@ function template_options()
 			'label' => $txt['popup_messages'],
 			'default' => true,
 		),
-		array(
-			'id' => 'copy_to_outbox',
-			'label' => $txt['copy_to_outbox'],
-			'default' => true,
-		),
 		array(
 			'id' => 'pm_remove_inbox_label',
 			'label' => $txt['pm_remove_inbox_label'],

+ 20 - 1
Themes/default/Stats.template.php

@@ -69,7 +69,10 @@ function template_main()
 							<dt>', $txt['latest_member'], ':</dt>
 							<dd>', $context['common_stats']['latest_member']['link'], '</dd>
 							<dt>', $txt['average_online'], ':</dt>
-							<dd>', $context['average_online'], '</dd>
+							<dd>', $context['average_online'], '</dd>';
+
+	if (!empty($context['gender']))
+		echo '
 							<dt>', $txt['gender_ratio'], ':</dt>
 							<dd>', $context['gender']['ratio'], '</dd>';
 
@@ -108,6 +111,9 @@ function template_main()
 									<div class="bar" style="width: ', $poster['post_percent'] + 4, 'px;">
 										<div style="width: ', $poster['post_percent'], 'px;"></div>
 									</div>';
+		else
+			echo '
+									<div class="bar empty"></div>';
 
 		echo '
 									<span class="righttext">', $poster['num_posts'], '</span>
@@ -142,6 +148,10 @@ function template_main()
 									<div class="bar" style="width: ', $board['post_percent'] + 4, 'px;">
 										<div style="width: ', $board['post_percent'], 'px;"></div>
 									</div>';
+		else
+			echo '
+									<div class="bar empty"></div>';
+
 		echo '
 									<span class="righttext">', $board['num_posts'], '</span>
 								</dd>';
@@ -176,6 +186,9 @@ function template_main()
 									<div class="bar" style="width: ', $topic['post_percent'] + 4, 'px;">
 										<div style="width: ', $topic['post_percent'], 'px;"></div>
 									</div>';
+		else
+			echo '
+									<div class="bar empty"></div>';
 
 		echo '
 									<span class="righttext">' . $topic['num_replies'] . '</span>
@@ -208,6 +221,9 @@ function template_main()
 								<div class="bar" style="width: ', $topic['post_percent'] + 4, 'px;">
 									<div style="width: ', $topic['post_percent'], 'px;"></div>
 								</div>';
+		else
+			echo '
+									<div class="bar empty"></div>';
 
 		echo '
 								<span class="righttext">' . $topic['num_views'] . '</span>
@@ -278,6 +294,9 @@ function template_main()
 								<div class="bar" style="width: ', $poster['time_percent'] + 4, 'px;">
 									<div style="width: ', $poster['time_percent'], 'px;"></div>
 								</div>';
+		else
+			echo '
+									<div class="bar empty"></div>';
 
 		echo '
 								<span>', $poster['time_online'], '</span>

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

@@ -718,7 +718,7 @@ function template_pick()
 			function changeVariant', $theme['id'], '(sVariant)
 			{
 				document.getElementById(\'theme_thumb_', $theme['id'], '\').src = oThumbnails', $theme['id'], '[sVariant];
-				document.getElementById(\'theme_use_', $theme['id'], '\').href = sBaseUseUrl', $theme['id'], ' + \';vrt=\' + sVariant;
+				document.getElementById(\'theme_use_', $theme['id'], '\').href = sBaseUseUrl', $theme['id'] == 0 ? $context['default_theme_id'] : $theme['id'], ' + \';vrt=\' + sVariant;
 				document.getElementById(\'theme_thumb_preview_', $theme['id'], '\').href = sBasePreviewUrl', $theme['id'], ' + \';vrt=\' + sVariant + \';variant=\' + sVariant;
 				document.getElementById(\'theme_preview_', $theme['id'], '\').href = sBasePreviewUrl', $theme['id'], ' + \';vrt=\' + sVariant + \';variant=\' + sVariant;
 			}

+ 0 - 2
Themes/default/Wireless.template.php

@@ -597,7 +597,6 @@ function template_imode_pm()
 						</tr></td>
 						<tr><td>
 							<input type="submit" value="', $txt['send_message'], '" class="button_submit" />
-							<input type="hidden" name="outbox" value="', $context['copy_to_outbox'] ? '1' : '0', '" />
 							<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
 							<input type="hidden" name="seqnum" value="', $context['form_sequence_number'], '" />
 							<input type="hidden" name="replied_to" value="', !empty($context['quoted_message']['id']) ? $context['quoted_message']['id'] : 0, '" />
@@ -1249,7 +1248,6 @@ function template_wap2_pm()
 					</p>
 					<p class="windowbg">
 						<input type="submit" value="', $txt['send_message'], '" class="button_submit" />
-						<input type="hidden" name="outbox" value="', $context['copy_to_outbox'] ? '1' : '0', '" />
 						<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
 						<input type="hidden" name="seqnum" value="', $context['form_sequence_number'], '" />
 						<input type="hidden" name="replied_to" value="', !empty($context['quoted_message']['id']) ? $context['quoted_message']['id'] : 0, '" />

+ 26 - 8
Themes/default/css/admin.css

@@ -162,7 +162,7 @@ div.quick_tasks {
 #mailqueue_stats .windowbg .content {
 	padding: 12px 9px 0 9px;
 }
-#admin_form_wrapper div.cat_bar:first-child, #admin_newsletters div.cat_bar:first-child,
+#admin_newsletters div.cat_bar:first-child,
 #generate_reports_type div.cat_bar:first-child, #groupForm div.cat_bar:first-child,
 #admin_form_wrapper div.title_bar, #mailqueue_stats div.cat_bar:first-child,
 #new_group div.cat_bar:first-child, #view_group div.cat_bar, #view_group div.cat_bar:first-child {
@@ -171,7 +171,7 @@ div.quick_tasks {
 	border-bottom: 1px solid #fff;
 	box-shadow: none;
 }
-#admin_form_wrapper div.cat_bar:first-child h3.catbg, #admin_newsletters div.cat_bar:first-child h3.catbg,
+#admin_newsletters div.cat_bar:first-child h3.catbg,
 #generate_reports_type div.cat_bar:first-child h3.catbg, #groupForm div.cat_bar:first-child h3.catbg, #mailqueue_stats div.cat_bar:first-child  h3.catbg,
 #new_group div.cat_bar:first-child  h3.catbg, #view_group div.cat_bar  h3.catbg, #view_group div.cat_bar:first-child  h3.catbg {
 	color: #444;
@@ -181,8 +181,6 @@ div.quick_tasks {
 	border-bottom: 1px solid #bbb;
 }
 #admin_form_wrapper div.title_bar {
-
-	border: 1px solid #ccc;
 	border-radius: 2px 2px 0 0;
 	margin: 0;
 	background: linear-gradient(bottom, #FFFFFF 1%, #F1F3F5 96%);
@@ -409,10 +407,9 @@ body#chrome  #quick_search .button_submit {
 /* Admin and moderation could generally do with a clean up everywhere.
 /* Live news from smf.org and support information. */
 #admin_main_section {
-	margin: 12px 0 0 0;
-	padding: 8px 12px 4px 12px;
-	border-radius: 7px 7px 0 0;
-	border-bottom: none;
+	margin: 12px 0;
+	padding: 8px 12px;
+	border-radius: 7px;
 }
 #admin_main_section .cat_bar {
 	background: none;
@@ -472,6 +469,20 @@ body#chrome  #quick_search .button_submit {
 	border-top: double #ddd;
 }
 
+fieldset.admin_group legend {
+	background: #eaf1f4;
+	border: 1px solid #cacdd3;
+	padding: 1px 5px;
+	border-radius: 3px;
+}
+fieldset.admin_group a {
+	display: inline-block;
+	width: 100px;
+	font-size: 85%;
+	text-align: center;
+	vertical-align: top;
+}
+
 /* The update warning. */
 #update_section {
 	margin: 6px 0;
@@ -927,6 +938,13 @@ dl.right dt {
 	margin: 0 0 6px 0;
 }
 
+/* Styles for the question and answers
+------------------------------------------------- */
+fieldset.qa_fieldset {
+	clear: both;
+	display: none;
+}
+
 /* Styles for the manage calendar section.
 ------------------------------------------------- */
 dl.settings dt.small_caption {

+ 27 - 133
Themes/default/css/index.css

@@ -544,6 +544,10 @@ div.pagesection div.floatright input, div.pagesection div.floatright select {
 .navPages {
 	padding: 0 1px;
 }
+.expand_pages {
+	font-weight: bold;
+	cursor: pointer;
+}
 .next_page, .previous_page {
 	text-transform: capitalize;
 	padding: 0 2px;
@@ -1211,111 +1215,6 @@ img.sort {
 	overflow: hidden;
 }
 
-/* Styles for sidebar menus.
-------------------------------------------------------- */
-#main_container {
-	position: relative;
-}
-#main_container:after {
-	content:"";
-	display: block;
-	clear: both;
-}
-#main_admsection {
-	position: relative;
-	left: 0;
-	right: 0;
-	overflow: auto;
-}
-#left_admsection {
-	width: 180px;
-	float: left;
-	padding: 0 10px 1em 0;
-}
-.adm_section .cat_bar {
-	background: #f0f4f7;
-	border: 1px solid #c8c8c8;
-	border-left: 1px solid #d8d8d8;
-	border-top: 1px solid #e2e2e2;
-	border-radius: 4px 4px 0 0;
-	box-shadow: 0 -2px 4px rgba(0,0,0,0.07) inset;
-	margin: 0;
-}
-.adm_section h4.catbg {
-	padding: 6px 8px;
-	font-size: 1em;
-	color: #444;
-	text-shadow: none;
-}
-.adm_section .dropmenu {
-	padding: 4px 1px 9px 1px;
-	background: linear-gradient(bottom, #FFFFFF 1%, #F1F3F5 96%);
-	background: -o-linear-gradient(bottom, #FFFFFF 1%, #F1F3F5 96%);
-	background: -moz-linear-gradient(bottom, #FFFFFF 1%, #F1F3F5 96%);
-	background: -webkit-linear-gradient(bottom, #FFFFFF 1%, #F1F3F5 96%);
-	background: -ms-linear-gradient(bottom, #FFFFFF 1%, #F1F3F5 96%);
-	float: none;
-}
-.adm_section .dropmenu li {
-	margin: 2px 0;
-	padding: 0;
-	line-height: 1.7em;
-	border: 1px solid transparent;
-	border-radius: 2px;
-	float: none;
-}
-.adm_section .dropmenu li:hover, .adm_section .dropmenu li.sfhover, #menu_current_area {
-	background: #fafafa;
-	box-shadow: -1px -1px 2px rgba(0,0,0,0.1), 2px 2px 2px rgba(96,134,166,0.07) inset;
-	border: 1px solid #dfdfdf;
-	border-left: 1px solid #cfcfcf;
-	border-top: 1px solid #c7c7c7;
-}
-.adm_section .dropmenu a, .adm_section .dropmenu a:hover, .adm_section .dropmenu li:hover a,
-.adm_section .dropmenu li.sfhover a, .adm_section .dropmenu>li>a:focus, #menu_current_area>strong>a:focus {
-	background: none;
-	text-decoration: none;
-	border: none;
-	padding: 3px 7px 1px 7px;
-}
-.adm_section .dropmenu a:hover, .adm_section .dropmenu li:hover a, .adm_section .dropmenu li.sfhover a, .adm_section .dropmenu>li a:focus {
-	color: #333;
-}
-.adm_section .dropmenu li ul {
-	top: -1px;
-}
-.adm_section .dropmenu li:hover ul, .adm_section .dropmenu li.sfhover ul {
-	left: 155px;
-}
-.adm_section .dropmenu li li, .adm_section .dropmenu li li:hover {
-	padding: 0;
-	background: none;
-	box-shadow: none;
-}
-.adm_section .dropmenu li li a, .adm_section .dropmenu li:hover li a, .adm_section .dropmenu li.sfhover li a {
-	color: #346;
-	padding: 0 7px;
-}
-.adm_section .dropmenu li li a:hover {
-	color: #333;
-	background: linear-gradient(bottom, #E2E9F3 1%, #FFFFFF 70%);
-	background: -o-linear-gradient(bottom, #E2E9F3 1%, #FFFFFF 70%);
-	background: -moz-linear-gradient(bottom, #E2E9F3 1%, #FFFFFF 70%);
-	background: -webkit-linear-gradient(bottom, #E2E9F3 1%, #FFFFFF 70%);
-	background: -ms-linear-gradient(bottom, #E2E9F3 1%, #FFFFFF 70%);
-}
-/* Note: The next declarations are for keyboard access with js disabled. */
-.adm_section .dropmenu ul a:focus {
-	margin-left: 10150px;
-	width: 17em;
-}
-/* Cancel those for hover and/or js access. */
-.adm_section .dropmenu ul li:hover a:focus, .adm_section .dropmenu ul li.sfhover a:focus {
-	margin-left: 0;
-	width: auto;
-}
-/*End sidebar flyout coding. */
-
 /* Styles for the standard button lists.
 ------------------------------------------------------- */
 
@@ -1438,6 +1337,7 @@ img.sort {
 }
 #top_section ul li {
 	margin-bottom: 2px;
+	margin-right: 7px;
 	display: inline;
 	font-size: 0.9em;
 }
@@ -1445,6 +1345,17 @@ img.sort {
 {
  font-weight: bold;
 }
+.modnotice
+{
+	padding: 4px;
+}
+.modnotice .amt
+{
+	padding: 0 4px;
+	color: white;
+	background: #6d90ad;
+	border-radius: 4px;
+}
 #search_form {
 	padding: 4px 0 0 0;
 	text-align: right;
@@ -3225,30 +3136,6 @@ dl {
 #avatar_upload {
 	overflow: auto;
 }
-#main_admsection #basicinfo, #main_admsection #detailedinfo {
-	width: 100%;
-}
-#main_admsection #basicinfo h4 {
-	float: left;
-	width: 35%;
-}
-#main_admsection #basicinfo img.avatar {
-	float: right;
-	vertical-align: top;
-}
-#main_admsection #basicinfo ul {
-	clear: left;
-}
-#main_admsection #basicinfo span#userstatus {
-	clear: left;
-}
-#main_admsection #basicinfo p#infolinks {
-	display: none;
-	clear: both;
-}
-#main_admsection #basicinfo .botslice {
-	clear: both;
-}
 
 /* Profile statistics */
 #generalstats div.content dt {
@@ -3578,6 +3465,10 @@ dl.stats dd {
 	margin: 0 4px 0 0;
 	height: 1.4em;
 }
+.statsbar div.bar.empty {
+	width: 0;
+	border-right: none;
+}
 /* Absolute positioning stops these breaking the bars on narrow screens. */
 dl.stats dd span {
 	padding: 0 2px;
@@ -3928,6 +3819,9 @@ span.hidelink {
 #searchform p.clear {
 	clear: both;
 }
+#searchform .roundframe h4.titlebg {
+	border-bottom: none;
+}
 
 /* Styles for the search results page.
 ------------------------------------------------- */
@@ -4299,7 +4193,7 @@ div#editlang_desc {
 #show_attachments th {
 	text-align: left;
 }
-#info_center .cat_bar {
+#info_center .cat_bar, #searchform .roundframe .title_bar {
 	border-right: 1px solid #ddd;
 	border-left: 1px solid #ddd;
 	margin: 0 -1px;
@@ -4313,17 +4207,17 @@ tr.catbg th:first-child, #show_attachments th:first-child {
 tr.catbg th:last-child, #show_attachments th:last-child {
 	border-right: 1px solid #ddd;
 }
-#info_center .catbg {
+#info_center .catbg, #searchform .roundframe .titlebg {
 	padding: 6px 12px 5px 12px;
 	background: none;
 	font-size: 1.1em;
 }
-#info_center .catbg, #info_center .catbg a, .table_grid tr.catbg th a {
+#info_center .catbg, #info_center .catbg a, .table_grid tr.catbg th a, #searchform .roundframe .titlebg {
 	color: #555;
 	text-shadow: none;
 }
 #info_center .title_barIC, #postmodify .roundframe .title_bar,
-#activitytime .cat_bar, #popularposts .cat_bar, #popularactivity .cat_bar{
+#activitytime .cat_bar, #popularposts .cat_bar, #popularactivity .cat_bar {
 	background: none;
 	border-top: 1px solid #999;
 	border-bottom: 1px solid #fff;

+ 1 - 24
Themes/default/css/rtl.css

@@ -162,7 +162,7 @@ table.table_list a.collapse {
 	right: 14.5em;
 }
 /* Indicator for additional levels. Best in the anchor so it stays visible on hover. */
-.dropmenu li li.subsections a:after, .adm_section .dropmenu li.subsections a:after{
+.dropmenu li li.subsections a:after {
 	left: 2px;
 	right: auto;
 	background: url(../images/admin/subsection_rtl.png) no-repeat;
@@ -605,16 +605,6 @@ div#admin_menu {
 #admin_content {
 	clear: right;
 }
-/* Styles for sidebar menus.
-------------------------------------------------------- */
-#left_admsection {
-	float: right;
-	padding-right: 0;
-	padding-left: 10px;
-}
-.left_admmenu li {
-	padding: 0 0.5em 0 0;
-}
 /* Styles for generic tables.
 ------------------------------------------------------- */
 .topic_table td.stickybg2 {
@@ -663,19 +653,6 @@ div#admin_menu {
 	float: right;
 }
 
-#main_admsection #basicinfo h4 {
-	float: right;
-}
-#main_admsection #basicinfo img.avatar {
-	float: left;
-}
-#main_admsection #basicinfo ul {
-	clear: right;
-}
-#main_admsection #basicinfo span#userstatus {
-	clear: right;
-}
-
 /* Profile statistics */
 #generalstats div.content dt {
 	float: right;

BIN
Themes/default/images/admin/big/attachment.png


BIN
Themes/default/images/admin/big/ban.png


BIN
Themes/default/images/admin/big/boards.png


BIN
Themes/default/images/admin/big/calendar.png


BIN
Themes/default/images/admin/big/current_theme.png


BIN
Themes/default/images/admin/big/default.png


BIN
Themes/default/images/admin/big/drafts.png


BIN
Themes/default/images/admin/big/engines.png


BIN
Themes/default/images/admin/big/features.png


BIN
Themes/default/images/admin/big/languages.png


BIN
Themes/default/images/admin/big/logs.png


BIN
Themes/default/images/admin/big/mail.png


BIN
Themes/default/images/admin/big/maintain.png


BIN
Themes/default/images/admin/big/membergroups.png


BIN
Themes/default/images/admin/big/members.png


BIN
Themes/default/images/admin/big/modifications.png


BIN
Themes/default/images/admin/big/news.png


BIN
Themes/default/images/admin/big/packages.png


BIN
Themes/default/images/admin/big/paid.png


BIN
Themes/default/images/admin/big/permissions.png


BIN
Themes/default/images/admin/big/posts.png


BIN
Themes/default/images/admin/big/regcenter.png


BIN
Themes/default/images/admin/big/reports.png


BIN
Themes/default/images/admin/big/scheduled.png


BIN
Themes/default/images/admin/big/search.png


BIN
Themes/default/images/admin/big/security.png


BIN
Themes/default/images/admin/big/server.png


BIN
Themes/default/images/admin/big/smiley.png


BIN
Themes/default/images/admin/big/support.png


BIN
Themes/default/images/admin/big/themes.png


BIN
Themes/default/images/admin/drafts.png


BIN
Themes/default/images/admin/feature_cp.png


BIN
Themes/default/images/admin/feature_dr.png


BIN
Themes/default/images/admin/feature_ih.png


BIN
Themes/default/images/admin/feature_rg.png


BIN
Themes/default/images/admin/features_and_options.png


BIN
Themes/default/images/admin/forum_maintenance.png


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