--- a/adm/style/acp_email.html
+++ b/adm/style/acp_email.html
@@ -47,6 +47,8 @@
 	<dd><input id="send" type="checkbox" class="radio" name="send_immediately" checked="checked" /></dd>
 </dl>
 
+<!-- EVENT acp_email_options_after -->
+
 <p class="submit-buttons">
 	<input class="button1" type="submit" id="submit" name="submit" value="{L_SEND_EMAIL}" />&nbsp;
 	<input class="button2" type="reset" id="reset" name="reset" value="{L_RESET}" />
--- a/adm/style/acp_forums.html
+++ b/adm/style/acp_forums.html
@@ -111,6 +111,7 @@
 
 	<fieldset>
 		<legend>{L_FORUM_SETTINGS}</legend>
+	<!-- EVENT acp_forums_main_settings_prepend -->
 	<dl>
 		<dt><label for="forum_type">{L_FORUM_TYPE}{L_COLON}</label></dt>
 		<dd><select id="forum_type" name="forum_type" onchange="display_options(this.options[this.selectedIndex].value);">{S_FORUM_TYPE_OPTIONS}</select></dd>
@@ -182,6 +183,7 @@
 		<dt><label for="forum_style">{L_FORUM_STYLE}{L_COLON}</label></dt>
 		<dd><select id="forum_style" name="forum_style"><option value="0">{L_DEFAULT_STYLE}</option>{S_STYLES_OPTIONS}</select></dd>
 	</dl>
+	<!-- EVENT acp_forums_main_settings_append -->
 	</fieldset>
 
 	<div id="forum_cat_options">
@@ -198,6 +200,7 @@
 	<div id="forum_post_options">
 		<fieldset>
 			<legend>{L_GENERAL_FORUM_SETTINGS}</legend>
+		<!-- EVENT acp_forums_normal_settings_prepend -->
 		<dl>
 			<dt><label for="forum_status">{L_FORUM_STATUS}{L_COLON}</label></dt>
 			<dd><select id="forum_status" name="forum_status">{S_STATUS_OPTIONS}</select></dd>
@@ -246,6 +249,7 @@
 
 		<fieldset>
 			<legend>{L_FORUM_PRUNE_SETTINGS}</legend>
+		<!-- EVENT acp_forums_prune_settings_prepend -->
 		<dl>
 			<dt><label for="enable_prune">{L_FORUM_AUTO_PRUNE}{L_COLON}</label><br /><span>{L_FORUM_AUTO_PRUNE_EXPLAIN}</span></dt>
 			<dd><label><input type="radio" class="radio" name="enable_prune" value="1"<!-- IF S_PRUNE_ENABLE --> id="enable_prune" checked="checked"<!-- ENDIF --> /> {L_YES}</label>
@@ -291,6 +295,7 @@
 			<dt><label for="prune_shadow_days">{L_AUTO_PRUNE_SHADOW_DAYS}{L_COLON}</label><br /><span>{L_AUTO_PRUNE_SHADOW_DAYS_EXPLAIN}</span></dt>
 			<dd><input type="number" id="prune_shadow_days" name="prune_shadow_days" value="{PRUNE_SHADOW_DAYS}" maxlength="4" size="4" min="0" max="9999" /> {L_DAYS}</dd>
 		</dl>
+		<!-- EVENT acp_forums_prune_settings_append -->
 		</fieldset>
 	</div>
 
@@ -317,6 +322,7 @@
 	<div id="forum_rules_options">
 		<fieldset>
 			<legend>{L_FORUM_RULES}</legend>
+		<!-- EVENT acp_forums_rules_settings_prepend -->
 		<dl>
 			<dt><label for="forum_rules_link">{L_FORUM_RULES_LINK}{L_COLON}</label><br /><span>{L_FORUM_RULES_LINK_EXPLAIN}</span></dt>
 			<dd><input class="text medium" type="text" id="forum_rules_link" name="forum_rules_link" value="{FORUM_RULES_LINK}" maxlength="255" /></dd>
@@ -334,6 +340,7 @@
 				<label><input type="checkbox" class="radio" name="rules_parse_smilies"<!-- IF S_SMILIES_CHECKED --> checked="checked"<!-- ENDIF --> /> {L_PARSE_SMILIES}</label>
 				<label><input type="checkbox" class="radio" name="rules_parse_urls"<!-- IF S_URLS_CHECKED --> checked="checked"<!-- ENDIF --> /> {L_PARSE_URLS}</label></dd>
 		</dl>
+		<!-- EVENT acp_forums_rules_settings_append -->
 		</fieldset>
 	</div>
 
--- a/assets/javascript/core.js
+++ b/assets/javascript/core.js
@@ -386,7 +386,8 @@ phpbb.ajaxify = function(options) {
 				type: method,
 				data: data,
 				success: returnHandler,
-				error: errorHandler
+				error: errorHandler,
+				cache: false
 			});
 			request.always(function() {
 				$loadingIndicator.fadeOut(phpbb.alertTime);
--- a/assets/javascript/plupload.js
+++ b/assets/javascript/plupload.js
@@ -34,6 +34,14 @@ phpbb.plupload.initialize = function() {
 		if (uploader.features.dragdrop) {
 			$('#drag-n-drop-message').show();
 		}
+
+		// Ensure "Add files" button position is correctly calculated.
+		if ($('#attach-panel-multi').is(':visible')) {
+			uploader.refresh();
+		}
+		$('[data-subpanel="attach-panel"]').one('click', function() {
+			uploader.refresh();
+		});
 	});
 };
 
--- a/composer.lock
+++ b/composer.lock
@@ -703,21 +703,21 @@
     "packages-dev": [
         {
             "name": "fabpot/goutte",
-            "version": "v1.0.3",
+            "version": "v1.0.7",
             "source": {
                 "type": "git",
-                "url": "https://github.com/fabpot/Goutte.git",
-                "reference": "75c9f23c4122caf4ea3e87a42a00b471366e707f"
+                "url": "https://github.com/FriendsOfPHP/Goutte.git",
+                "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/fabpot/Goutte/zipball/75c9f23c4122caf4ea3e87a42a00b471366e707f",
-                "reference": "75c9f23c4122caf4ea3e87a42a00b471366e707f",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/794b196e76bdd37b5155cdecbad311f0a3b07625",
+                "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625",
                 "shasum": ""
             },
             "require": {
                 "ext-curl": "*",
-                "guzzle/http": ">=3.0.5,<3.8-dev",
+                "guzzle/http": "~3.1",
                 "php": ">=5.3.0",
                 "symfony/browser-kit": "~2.1",
                 "symfony/css-selector": "~2.1",
@@ -726,8 +726,8 @@
                 "symfony/process": "~2.1"
             },
             "require-dev": {
-                "guzzle/plugin-history": ">=3.0.5,<3.8-dev",
-                "guzzle/plugin-mock": ">=3.0.5,<3.8-dev"
+                "guzzle/plugin-history": "~3.1",
+                "guzzle/plugin-mock": "~3.1"
             },
             "type": "application",
             "extra": {
@@ -747,9 +747,7 @@
             "authors": [
                 {
                     "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com",
-                    "homepage": "http://fabien.potencier.org",
-                    "role": "Lead Developer"
+                    "email": "fabien@symfony.com"
                 }
             ],
             "description": "A simple PHP Web Scraper",
@@ -757,7 +755,7 @@
             "keywords": [
                 "scraper"
             ],
-            "time": "2013-08-16 06:03:22"
+            "time": "2014-10-09 15:52:51"
         },
         {
             "name": "guzzle/common",
@@ -1592,16 +1590,16 @@
         },
         {
             "name": "sami/sami",
-            "version": "v1.3",
+            "version": "v1.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/fabpot/Sami.git",
-                "reference": "76f2ed80b3420f7e2f6dcd5b7218b5a5781f4110"
+                "url": "https://github.com/FriendsOfPHP/Sami.git",
+                "reference": "70f29c781f7bef30181c814b9471b2ceac694454"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/fabpot/Sami/zipball/76f2ed80b3420f7e2f6dcd5b7218b5a5781f4110",
-                "reference": "76f2ed80b3420f7e2f6dcd5b7218b5a5781f4110",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/70f29c781f7bef30181c814b9471b2ceac694454",
+                "reference": "70f29c781f7bef30181c814b9471b2ceac694454",
                 "shasum": ""
             },
             "require": {
@@ -1622,7 +1620,7 @@
             "type": "application",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.3-dev"
+                    "dev-master": "1.4-dev"
                 }
             },
             "autoload": {
@@ -1637,9 +1635,7 @@
             "authors": [
                 {
                     "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com",
-                    "homepage": "http://fabien.potencier.org",
-                    "role": "Lead Developer"
+                    "email": "fabien@symfony.com"
                 }
             ],
             "description": "Sami, an API documentation generator",
@@ -1647,7 +1643,7 @@
             "keywords": [
                 "phpdoc"
             ],
-            "time": "2013-11-30 17:16:25"
+            "time": "2014-06-25 11:24:03"
         },
         {
             "name": "sebastian/comparator",
--- a/config/services.yml
+++ b/config/services.yml
@@ -75,6 +75,7 @@ services:
             - @controller.provider
             - @ext.manager
             - @symfony_request
+            - @request
             - @filesystem
             - %core.root_path%
             - %core.php_ext%
@@ -108,6 +109,9 @@ services:
     filesystem:
         class: phpbb\filesystem
 
+    file_downloader:
+        class: phpbb\file_downloader
+
     http_kernel:
         class: Symfony\Component\HttpKernel\HttpKernel
         arguments:
@@ -155,6 +159,8 @@ services:
             - null
             - %core.disable_super_globals%
 
+    # WARNING: The Symfony request does not escape the input and should be used very carefully
+    #           prefer the phpbb request (service @request) as possible
     symfony_request:
         class: phpbb\symfony_request
         arguments:
@@ -178,4 +184,5 @@ services:
         arguments:
             - @cache
             - @config
+            - @file_downloader
             - @user
--- a/docs/CHANGELOG.html
+++ b/docs/CHANGELOG.html
@@ -46,6 +46,7 @@
 <ol>
 	<li><a href="#changelog">Changelog</a>
 	<ol style="list-style-type: lower-roman;">
+		<li><a href="#v311">Changes since 3.1.1</a></li>
 		<li><a href="#v310">Changes since 3.1.0</a></li>
 		<li><a href="#v310RC6">Changes since 3.1.0-RC6</a></li>
 		<li><a href="#v310RC5">Changes since 3.1.0-RC5</a></li>
@@ -101,7 +102,88 @@
 
 		<div class="content">
 
-	<a name="v310"></a><h3>1.i. Changes since 3.1.0</h3>
+	<a name="v311"></a><h3>1.i. Changes since 3.1.1</h3>
+
+	<h4>Security</h4>
+	<ul>
+		<li>[SECURITY-171] - Version helper does not properly escape version info</li>
+		<li>[SECURITY-169] - AJAX request with unexpected referrer causes infinite loop</li>
+	</ul>
+
+	<h4>Bug</h4>
+	<ul>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-10442">PHPBB3-10442</a>] - XHTML is invalid when a forum link without redirect counter is present</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-10744">PHPBB3-10744</a>] - Prevent user from installing styles with reserved directory names</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11863">PHPBB3-11863</a>] - User registration settings show incorrectly as disabled when board-wide emails are disabled</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12703">PHPBB3-12703</a>] - Notification System sends exact same SQL query multiple times</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13083">PHPBB3-13083</a>] - Language correction in NO_ENTRIES in acp_logs</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13100">PHPBB3-13100</a>] - Don't display &quot;delete reason&quot; dialog for shadow-topics</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13193">PHPBB3-13193</a>] - Post counts in Private Messages should link to the user's posts</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13197">PHPBB3-13197</a>] - Group Avatar not deleted from users</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13204">PHPBB3-13204</a>] - Login flood control error supresses incorrect credential error</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13209">PHPBB3-13209</a>] - Boolean (Yes/No) custom profile field doesn't show given name</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13216">PHPBB3-13216</a>] - Datetime tests fail randomly</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13228">PHPBB3-13228</a>] - &quot;Code: Select all&quot; font-size too big in Private Messages</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13239">PHPBB3-13239</a>] - Can´t upload Attachments on iOS</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13241">PHPBB3-13241</a>] - Topics are being duplicated in multipage forums</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13242">PHPBB3-13242</a>] - Validation error in Contact a Board Administrator</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13243">PHPBB3-13243</a>] - Debug error when clicking Re-check all versions on ACP manage extensions page</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13251">PHPBB3-13251</a>] - Database password containing special characters no longer accepted after upgrade to 3.1.0</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13253">PHPBB3-13253</a>] - MCP queue link in active topics search is missing</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13265">PHPBB3-13265</a>] - &quot;Edit profile&quot; link on view-own-profile page should only show if user has permission to edit</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13270">PHPBB3-13270</a>] - Upgrading from 3.0.12 to 3.1.1 does not display moderator soft delete permissions</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13277">PHPBB3-13277</a>] - Move Up &amp; Down does not take work in Internet Explorer</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13280">PHPBB3-13280</a>] - $user-&gt;page['page'] - is invalid resulting in confirm_box() not working correctly</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13284">PHPBB3-13284</a>] - Message body not included in email topic message </li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13298">PHPBB3-13298</a>] - Use mysql_free_result to free result sets which were requested using mysql_query()</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13300">PHPBB3-13300</a>] - Jabber field still shown in profile when feature is disabled</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13301">PHPBB3-13301</a>] - Apache Authentication is probably broken</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13303">PHPBB3-13303</a>] - Migrator caught in loop calculating dependencies</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13315">PHPBB3-13315</a>] - Upgrade from 3.0.12 to 3.1.1 resets CAPTCHA selection</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13316">PHPBB3-13316</a>] - reCAPTCHA does not work on secured connection</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13318">PHPBB3-13318</a>] - login_username doesn't have multibyte parameter set to true</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13323">PHPBB3-13323</a>] - posting.php can pass invalid auth option to acl_get()</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13332">PHPBB3-13332</a>] - Insufficient information passed to password drivers for converted boards</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13337">PHPBB3-13337</a>] - Mark subforums read triggers error if subforums contain no topics</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13338">PHPBB3-13338</a>] - Some tests fail when run on their own</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13342">PHPBB3-13342</a>] - 310/captcha_plugins migration changes recaptcha to nogd</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13349">PHPBB3-13349</a>] - Incorrect entities used for breadcrumb separator in CSS</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13354">PHPBB3-13354</a>] - Unknown column 'topic_logs' in 'where clause' when deleting topic log in MCP</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13376">PHPBB3-13376</a>] - deregister_globals() does not work correctly when $_COOKIE['GLOBALS'] is specified</li>
+	</ul>
+
+	<h4>Improvement</h4>
+	<ul>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12681">PHPBB3-12681</a>] - Cache the compiled routes and dump the url_generator</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12885">PHPBB3-12885</a>] - Wrong index page title when using Board Index text</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13023">PHPBB3-13023</a>] - [event] - Add Event posting_editor_buttons_custom_tags_before</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13133">PHPBB3-13133</a>] - Allow @vendor_extname in INCLUDECSS</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13182">PHPBB3-13182</a>] - [event] - Add posting.php core event to allow modifying the message before parsing</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13220">PHPBB3-13220</a>] - [event] - Add template events to memberlist_search.html</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13290">PHPBB3-13290</a>] - [event] - Add template event index_body_forumlist_body_after</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13294">PHPBB3-13294</a>] - [event] - Add message_parser.php core event for additional message handling before parsing</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13297">PHPBB3-13297</a>] - Add unicode modifier to url/email regular expression patterns</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13309">PHPBB3-13309</a>] - [event] - Add ACP template event acp_email_options_after</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13310">PHPBB3-13310</a>] - [event] - Add core event core.acp_email_modify_sql</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13326">PHPBB3-13326</a>] - Add viewtopic_url variable to a viewtopic event</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13328">PHPBB3-13328</a>] - [event] - Add event core.mcp_view_forum_modify_sql</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13347">PHPBB3-13347</a>] - [event] - Add new template events to acp_forums.html</li>
+	</ul>
+
+	<h4>New Feature</h4>
+	<ul>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12962">PHPBB3-12962</a>] - Use phantomjs and webdriver for UI testing</li>
+	</ul>
+
+	<h4>Task</h4>
+	<ul>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13324">PHPBB3-13324</a>] - Composer no longer downloads sami/sami and fabpot/goutte</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13325">PHPBB3-13325</a>] - Make installing dependencies for tests more user friendly or optional</li>
+		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13331">PHPBB3-13331</a>] - Sami run as part of phing MUST NOT switch branches</li>
+	</ul>
+
+
+	<a name="v310"></a><h3>1.ii. Changes since 3.1.0</h3>
 
 	<h4>Security</h4>
 	<ul>
@@ -119,7 +201,7 @@
 		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13262">PHPBB3-13262</a>] - Add note to docs about htaccess file when upgrading 3.0 to 3.1</li>
 	</ul>
 
-	<a name="v310RC6"></a><h3>1.i. Changes since 3.1.0-RC6</h3>
+	<a name="v310RC6"></a><h3>1.iii. Changes since 3.1.0-RC6</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -147,7 +229,7 @@
 		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13215">PHPBB3-13215</a>] - Update Symfony Components to 2.3.21</li>
 	</ul>
 
-	<a name="v310RC5"></a><h3>1.ii. Changes since 3.1.0-RC5</h3>
+	<a name="v310RC5"></a><h3>1.iv. Changes since 3.1.0-RC5</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -188,7 +270,7 @@
 	</ul>
 
 
-	<a name="v310RC4"></a><h3>1.iii. Changes since 3.1.0-RC4</h3>
+	<a name="v310RC4"></a><h3>1.v. Changes since 3.1.0-RC4</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -255,7 +337,7 @@
 		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13123">PHPBB3-13123</a>] - Add events to allow post blocking and post pre/past processing</li>
 	</ul>
 
-	<a name="v310RC3"></a><h3>1.iv. Changes since 3.1.0-RC3</h3>
+	<a name="v310RC3"></a><h3>1.vi. Changes since 3.1.0-RC3</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -345,7 +427,7 @@
 	</ul>
 
 
-	<a name="v310RC2"></a><h3>1.v. Changes since 3.1.0-RC2</h3>
+	<a name="v310RC2"></a><h3>1.vii. Changes since 3.1.0-RC2</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -469,7 +551,7 @@
 		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12948">PHPBB3-12948</a>] - Remove Travis CI &quot;broken opcache on PHP 5.5.7 and 5.5.8&quot; workaround.</li>
 	</ul>
 
-	<a name="v310RC1"></a><h3>1.vi. Changes since 3.1.0-RC1</h3>
+	<a name="v310RC1"></a><h3>1.viii. Changes since 3.1.0-RC1</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -540,7 +622,7 @@
 		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12829">PHPBB3-12829</a>] - Remove check for pgsql 8.3/8.2</li>
 	</ul>
 
-	<a name="v310b4"></a><h3>1.vii. Changes since 3.1.0-b4</h3>
+	<a name="v310b4"></a><h3>1.ix. Changes since 3.1.0-b4</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -660,7 +742,7 @@
 	</ul>
 
 
-	<a name="v310b3"></a><h3>1.viii. Changes since 3.1.0-b3</h3>
+	<a name="v310b3"></a><h3>1.x. Changes since 3.1.0-b3</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -767,7 +849,7 @@
 	</ul>
 
 
-	<a name="v310b2"></a><h3>1.ix. Changes since 3.1.0-b2</h3>
+	<a name="v310b2"></a><h3>1.xi. Changes since 3.1.0-b2</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -932,7 +1014,7 @@
 	</ul>
 
 
-	<a name="v310b1"></a><h3>1.x. Changes since 3.1.0-b1</h3>
+	<a name="v310b1"></a><h3>1.xii. Changes since 3.1.0-b1</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -1000,7 +1082,7 @@
 		<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12302">PHPBB3-12302</a>] - Upgrade composer.phar to 1.0.0-alpha8</li>
 	</ul>
 
-	<a name="v310a3"></a><h3>1.xi. Changes since 3.1.0-a3</h3>
+	<a name="v310a3"></a><h3>1.xiii. Changes since 3.1.0-a3</h3>
 
 	<h4>Bug</h4>
 	<ul>
@@ -1147,7 +1229,7 @@
 	</ul>
 
 
-	<a name="v310a2"></a><h3>1.xii. Changes since 3.1.0-a2</h3>
+	<a name="v310a2"></a><h3>1.xiv. Changes since 3.1.0-a2</h3>
 
 <h4>Bug</h4>
 <ul>
@@ -1255,7 +1337,7 @@
 <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12147">PHPBB3-12147</a>] - Remove Travis CI notification configuration</li>
 </ul>
 
-	<a name="v310a1"></a><h3>1.xiii. Changes since 3.1.0-a1</h3>
+	<a name="v310a1"></a><h3>1.xv. Changes since 3.1.0-a1</h3>
 
 <h4>Bug</h4>
 <ul>
@@ -1331,7 +1413,7 @@
 <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11998">PHPBB3-11998</a>] - Add console / command line client environment </li>
 </ul>
 
-	<a name="v30x"></a><h3>1.xiv. Changes since 3.0.x</h3>
+	<a name="v30x"></a><h3>1.xvi. Changes since 3.0.x</h3>
 
 <h4>Bug</h4>
 <ul>
@@ -2012,7 +2094,7 @@
 <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11913">PHPBB3-11913</a>] - Apply reorganisation of download.phpbb.com to build_announcement.php</li>
 </ul>
 
-	<a name="v3011"></a><h3>1.xv. Changes since 3.0.11</h3>
+	<a name="v3011"></a><h3>1.xvii. Changes since 3.0.11</h3>
 
 <h4>Bug</h4>
 <ul>
@@ -2167,7 +2249,7 @@
 <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11753">PHPBB3-11753</a>] - Upgrade mysql_upgrader.php schema data.</li>
 </ul>
 
-	<a name="v3010"></a><h3>1.xvi. Changes since 3.0.10</h3>
+	<a name="v3010"></a><h3>1.xviii. Changes since 3.0.10</h3>
 
 <h4>Bug</h4>
 <ul>
@@ -2292,7 +2374,7 @@
 <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-10909">PHPBB3-10909</a>] - Update Travis Test Configuration: Travis no longer supports PHP 5.3.2</li>
 </ul>
 
-	<a name="v309"></a><h3>1.xvii. Changes since 3.0.9</h3>
+	<a name="v309"></a><h3>1.xix. Changes since 3.0.9</h3>
 
 <h4>Bug</h4>
 <ul>
@@ -2428,7 +2510,7 @@
 <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-10480">PHPBB3-10480</a>] - Automate changelog building</li>
 </ul>
 
-	<a name="v308"></a><h3>1.xviii. Changes since 3.0.8</h3>
+	<a name="v308"></a><h3>1.xx. Changes since 3.0.8</h3>
 
 <h4>        Bug
 </h4>
@@ -2796,7 +2878,7 @@
 </ul>
 
 
-	<a name="v307-PL1"></a><h3>1.xix. Changes since 3.0.7-PL1</h3>
+	<a name="v307-PL1"></a><h3>1.xxi. Changes since 3.0.7-PL1</h3>
 <h4>        Security
 </h4>
 <ul>
@@ -3254,13 +3336,13 @@
 </ul>
 
 
-	<a name="v307"></a><h3>1.xx. Changes since 3.0.7</h3>
+	<a name="v307"></a><h3>1.xxii. Changes since 3.0.7</h3>
 
 	<ul>
 		<li>[Sec] Do not expose forum content of forums with ACL entries but no actual permission in ATOM Feeds. (Bug #58595)</li>
 	</ul>
 
-	<a name="v306"></a><h3>1.xxi. Changes since 3.0.6</h3>
+	<a name="v306"></a><h3>1.xxiii. Changes since 3.0.6</h3>
 
 	<ul>
 		<li>[Fix] Allow ban reason and length to be selected and copied in ACP and subsilver2 MCP. (Bug #51095)</li>
@@ -3364,7 +3446,7 @@
 
 	</ul>
 
-	<a name="v305"></a><h3>1.xxii. Changes since 3.0.5</h3>
+	<a name="v305"></a><h3>1.xxiv. Changes since 3.0.5</h3>
 
 	<ul>
 		<li>[Fix] Allow whitespaces in avatar gallery names. (Bug #44955)</li>
@@ -3586,7 +3668,7 @@
 		<li>[Feature] Send anonymous statistical information to phpBB on installation and update (optional).</li>
 	</ul>
 
-	<a name="v304"></a><h3>1.xxiii. Changes since 3.0.4</h3>
+	<a name="v304"></a><h3>1.xxv. Changes since 3.0.4</h3>
 
 	<ul>
 		<li>[Fix] Delete user entry from ban list table upon user deletion (Bug #40015 - Patch by TerraFrost)</li>
@@ -3675,7 +3757,7 @@
 		<li>[Sec] Only use forum id supplied for posting if global announcement detected. (Reported by nickvergessen)</li>
 	</ul>
 
-	<a name="v303"></a><h3>1.xxiv. Changes since 3.0.3</h3>
+	<a name="v303"></a><h3>1.xxvi. Changes since 3.0.3</h3>
 
 	<ul>
 		<li>[Fix] Allow mixed-case template directories to be inherited (Bug #36725)</li>
@@ -3707,7 +3789,7 @@
 		<li>[Sec] Ask for forum password if post within passworded forum quoted in private message. (Reported by nickvergessen)</li>
 	</ul>
 
-	<a name="v302"></a><h3>1.xxv. Changes since 3.0.2</h3>
+	<a name="v302"></a><h3>1.xxvii. Changes since 3.0.2</h3>
 
 	<ul>
 		<li>[Fix] Correctly set topic starter if first post in topic removed (Bug #30575 - Patch by blueray2048)</li>
@@ -3806,7 +3888,7 @@
 		<li>[Sec Precaution] Stricter validation of the HTTP_HOST header (Thanks to Techie-Micheal et al for pointing out possible issues in derived code)</li>
 	</ul>
 
-	<a name="v301"></a><h3>1.xxvi. Changes since 3.0.1</h3>
+	<a name="v301"></a><h3>1.xxviii. Changes since 3.0.1</h3>
 
 	<ul>
 		<li>[Fix] Ability to set permissions on non-mysql dbms (Bug #24955)</li>
@@ -3854,7 +3936,7 @@
 		<li>[Sec] Only allow urls gone through redirect() being used within login_box(). (thanks nookieman)</li>
 	</ul>
 
-	<a name="v300"></a><h3>1.xxvii. Changes since 3.0.0</h3>
+	<a name="v300"></a><h3>1.xxix. Changes since 3.0.0</h3>
 
 	<ul>
 		<li>[Change] Validate birthdays (Bug #15004)</li>
@@ -3925,7 +4007,7 @@
 		<li>[Fix] Find and display colliding usernames correctly when converting from one database to another (Bug #23925)</li>
 	</ul>
 
-	<a name="v30rc8"></a><h3>1.xxviii. Changes since 3.0.RC8</h3>
+	<a name="v30rc8"></a><h3>1.xxx. Changes since 3.0.RC8</h3>
 
 	<ul>
 		<li>[Fix] Cleaned usernames contain only single spaces, so &quot;a_name&quot; and &quot;a__name&quot; are treated as the same name (Bug #15634)</li>
@@ -3934,7 +4016,7 @@
 		<li>[Fix] Call garbage_collection() within database updater to correctly close connections (affects Oracle for example)</li>
 	</ul>
 
-	<a name="v30rc7"></a><h3>1.xxix. Changes since 3.0.RC7</h3>
+	<a name="v30rc7"></a><h3>1.xxxi. Changes since 3.0.RC7</h3>
 
 	<ul>
 		<li>[Fix] Fixed MSSQL related bug in the update system</li>
@@ -3969,7 +4051,7 @@
 		<li>[Fix] No duplication of active topics (Bug #15474)</li>
 	</ul>
 
-	<a name="v30rc6"></a><h3>1.xxx. Changes since 3.0.RC6</h3>
+	<a name="v30rc6"></a><h3>1.xxxii. Changes since 3.0.RC6</h3>
 
 	<ul>
 		<li>[Fix] Submitting language changes using acp_language (Bug #14736)</li>
@@ -3979,7 +4061,7 @@
 		<li>[Fix] Able to request new password (Bug #14743)</li>
 	</ul>
 
-	<a name="v30rc5"></a><h3>1.xxxi. Changes since 3.0.RC5</h3>
+	<a name="v30rc5"></a><h3>1.xxxiii. Changes since 3.0.RC5</h3>
 
 	<ul>
 		<li>[Feature] Removing constant PHPBB_EMBEDDED in favor of using an exit_handler(); the constant was meant to achive this more or less.</li>
@@ -4042,7 +4124,7 @@
 		<li>[Sec] New password hashing mechanism for storing passwords (#i42)</li>
 	</ul>
 
-	<a name="v30rc4"></a><h3>1.xxxii. Changes since 3.0.RC4</h3>
+	<a name="v30rc4"></a><h3>1.xxxiv. Changes since 3.0.RC4</h3>
 
 	<ul>
 		<li>[Fix] MySQL, PostgreSQL and SQLite related database fixes (Bug #13862)</li>
@@ -4093,7 +4175,7 @@
 		<li>[Fix] odbc_autocommit causing existing result sets to be dropped (Bug #14182)</li>
 	</ul>
 
-	<a name="v30rc3"></a><h3>1.xxxiii. Changes since 3.0.RC3</h3>
+	<a name="v30rc3"></a><h3>1.xxxv. Changes since 3.0.RC3</h3>
 
 	<ul>
 		<li>[Fix] Fixing some subsilver2 and prosilver style issues</li>
@@ -4202,7 +4284,7 @@
 
 	</ul>
 
-	<a name="v30rc2"></a><h3>1.xxxiv. Changes since 3.0.RC2</h3>
+	<a name="v30rc2"></a><h3>1.xxxvi. Changes since 3.0.RC2</h3>
 
 	<ul>
 		<li>[Fix] Re-allow searching within the memberlist</li>
@@ -4248,7 +4330,7 @@
 
 	</ul>
 
-	<a name="v30rc1"></a><h3>1.xxxv. Changes since 3.0.RC1</h3>
+	<a name="v30rc1"></a><h3>1.xxxvii. Changes since 3.0.RC1</h3>
 
 	<ul>
 		<li>[Fix] (X)HTML issues within the templates (Bug #11255, #11255)</li>
--- a/docs/events.md
+++ b/docs/events.md
@@ -16,11 +16,59 @@ acp_bbcodes_edit_fieldsets_after
 * Since: 3.1.0-a3
 * Purpose: Add settings to BBCode add/edit form
 
+acp_email_options_after
+===
+* Location: adm/style/acp_email.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to mass email form
+
+acp_forums_main_settings_append
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums at end of main settings section
+
+acp_forums_main_settings_prepend
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums before main settings section
+
 acp_forums_normal_settings_append
 ===
 * Location: adm/style/acp_forums.html
 * Since: 3.1.0-a1
-* Purpose: Add settings to forums
+* Purpose: Add settings to forums at end of normal settings section
+
+acp_forums_normal_settings_prepend
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums before normal settings section
+
+acp_forums_prune_settings_append
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums at end of prune settings section
+
+acp_forums_prune_settings_prepend
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums before prune settings section
+
+acp_forums_rules_settings_append
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums at end of rules settings section
+
+acp_forums_rules_settings_prepend
+===
+* Location: adm/style/acp_forums.html
+* Since: 3.1.2-RC1
+* Purpose: Add settings to forums before rules settings section
 
 acp_group_options_before
 ===
@@ -111,7 +159,7 @@ acp_ranks_list_column_before
 * Locations:
     + adm/style/acp_ranks.html
 * Since: 3.1.0-RC3
-* Purpose: Add content after the last column (but before the action column) 
+* Purpose: Add content after the last column (but before the action column)
 in the ranks list in the ACP
 
 acp_ranks_list_header_after
@@ -126,7 +174,7 @@ acp_ranks_list_header_before
 * Locations:
     + adm/style/acp_ranks.html
 * Since: 3.1.0-RC3
-* Purpose: Add content after the last header-column (but before the action column) 
+* Purpose: Add content after the last header-column (but before the action column)
 in the ranks list in the ACP
 
 acp_simple_footer_after
@@ -336,6 +384,14 @@ index_body_block_stats_prepend
 * Since: 3.1.0-b3
 * Purpose: Prepend content to the statistics list on the Board index
 
+index_body_forumlist_body_after
+===
+* Locations:
+    + styles/prosilver/template/index_body.html
+    + styles/subsilver2/template/index_body.html
+* Since: 3.1.1
+* Purpose: Add content after the forum list body on the index page
+
 index_body_markforums_after
 ===
 * Locations:
@@ -418,6 +474,30 @@ memberlist_body_username_prepend
 * Purpose: Add information before every username in the memberlist. Works in
 all display modes (leader, group and normal memberlist).
 
+memberlist_search_fields_after
+===
+* Locations:
+    + styles/prosilver/template/memberlist_search.html
+    + styles/subsilver2/template/memberlist_search.html
+* Since: 3.1.2-RC1
+* Purpose: Add information after the search fields column.
+
+memberlist_search_fields_before
+===
+* Locations:
+    + styles/prosilver/template/memberlist_search.html
+    + styles/subsilver2/template/memberlist_search.html
+* Since: 3.1.2-RC1
+* Purpose: Add information before the search fields column.
+
+memberlist_search_sorting_options_before
+===
+* Locations:
+    + styles/prosilver/template/memberlist_search.html
+    + styles/subsilver2/template/memberlist_search.html
+* Since: 3.1.2-RC1
+* Purpose: Add information before the search sorting options field.
+
 memberlist_view_contact_after
 ===
 * Locations:
@@ -756,6 +836,14 @@ posting_editor_buttons_before
 * Since: 3.1.0-a3
 * Purpose: Add content before the BBCode posting buttons
 
+posting_editor_buttons_custom_tags_before
+===
+* Locations:
+    + styles/prosilver/template/posting_buttons.html
+    + styles/subsilver2/template/posting_buttons.html
+* Since: 3.1.2-RC1
+* Purpose: Add content inside the BBCode posting buttons and before the customs BBCode
+
 posting_editor_message_after
 ===
 * Locations:
@@ -1011,7 +1099,7 @@ ucp_pm_viewmessage_post_buttons_after
     + styles/prosilver/template/ucp_pm_viewmessage.html
     + styles/subsilver2/template/ucp_pm_viewmessage.html
 * Since: 3.1.0-RC3
-* Purpose: Add post button to private messages (next to edit, quote etc), at 
+* Purpose: Add post button to private messages (next to edit, quote etc), at
 the end of the list.
 
 ucp_pm_viewmessage_post_buttons_before
@@ -1020,7 +1108,7 @@ ucp_pm_viewmessage_post_buttons_before
     + styles/prosilver/template/ucp_pm_viewmessage.html
     + styles/subsilver2/template/ucp_pm_viewmessage.html
 * Since: 3.1.0-RC3
-* Purpose: Add post button to private messages (next to edit, quote etc), at 
+* Purpose: Add post button to private messages (next to edit, quote etc), at
 the start of the list.
 
 ucp_pm_viewmessage_print_head_append
--- a/includes/acp/acp_board.php
+++ b/includes/acp/acp_board.php
@@ -615,7 +615,15 @@ class acp_board
 		{
 			add_log('admin', 'LOG_CONFIG_' . strtoupper($mode));
 
-			trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action));
+			$message = $user->lang('CONFIG_UPDATED');
+			$message_type = E_USER_NOTICE;
+			if (!$config['email_enable'] && in_array($mode, array('email', 'registration')) &&
+				in_array($config['require_activation'], array(USER_ACTIVATION_SELF, USER_ACTIVATION_ADMIN)))
+			{
+				$message .= '<br /><br />' . $user->lang('ACC_ACTIVATION_WARNING');
+				$message_type = E_USER_WARNING;
+			}
+			trigger_error($message . adm_back_link($this->u_action), $message_type);
 		}
 
 		$this->tpl_name = 'acp_board';
@@ -792,20 +800,19 @@ class acp_board
 		global $user, $config;
 
 		$act_ary = array(
-			'ACC_DISABLE' => USER_ACTIVATION_DISABLE,
-			'ACC_NONE' => USER_ACTIVATION_NONE,
+			'ACC_DISABLE'	=> array(true, USER_ACTIVATION_DISABLE),
+			'ACC_NONE'		=> array(true, USER_ACTIVATION_NONE),
+			'ACC_USER'		=> array($config['email_enable'], USER_ACTIVATION_SELF),
+			'ACC_ADMIN'		=> array($config['email_enable'], USER_ACTIVATION_ADMIN),
 		);
-		if ($config['email_enable'])
-		{
-			$act_ary['ACC_USER'] = USER_ACTIVATION_SELF;
-			$act_ary['ACC_ADMIN'] = USER_ACTIVATION_ADMIN;
-		}
-		$act_options = '';
 
-		foreach ($act_ary as $key => $value)
+		$act_options = '';
+		foreach ($act_ary as $key => $data)
 		{
+			list($available, $value) = $data;
 			$selected = ($selected_value == $value) ? ' selected="selected"' : '';
-			$act_options .= '<option value="' . $value . '"' . $selected . '>' . $user->lang[$key] . '</option>';
+			$class = (!$available) ? ' class="disabled-option"' : '';
+			$act_options .= '<option value="' . $value . '"' . $selected . $class . '>' . $user->lang($key) . '</option>';
 		}
 
 		return $act_options;
--- a/includes/acp/acp_email.php
+++ b/includes/acp/acp_email.php
@@ -26,7 +26,7 @@ class acp_email
 	function main($id, $mode)
 	{
 		global $config, $db, $user, $auth, $template, $cache;
-		global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix;
+		global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix, $phpbb_dispatcher;
 
 		$user->add_lang('acp/email');
 		$this->tpl_name = 'acp_email';
@@ -72,11 +72,15 @@ class acp_email
 				if ($usernames)
 				{
 					// If giving usernames the admin is able to email inactive users too...
-					$sql = 'SELECT username, user_email, user_jabber, user_notify_type, user_lang
-						FROM ' . USERS_TABLE . '
-						WHERE ' . $db->sql_in_set('username_clean', array_map('utf8_clean_string', explode("\n", $usernames))) . '
-							AND user_allow_massemail = 1
-						ORDER BY user_lang, user_notify_type'; // , SUBSTRING(user_email FROM INSTR(user_email, '@'))
+					$sql_ary = array(
+						'SELECT'	=> 'username, user_email, user_jabber, user_notify_type, user_lang',
+						'FROM'		=> array(
+							USERS_TABLE		=> '',
+						),
+						'WHERE'		=> $db->sql_in_set('username_clean', array_map('utf8_clean_string', explode("\n", $usernames))) . '
+							AND user_allow_massemail = 1',
+						'ORDER_BY'	=> 'user_lang, user_notify_type',
+					);
 				}
 				else
 				{
@@ -123,8 +127,18 @@ class acp_email
 							),
 						);
 					}
-					$sql = $db->sql_build_query('SELECT', $sql_ary);
 				}
+				/**
+				* Modify sql query to change the list of users the email is sent to
+				*
+				* @event core.acp_email_modify_sql
+				* @var	array	sql_ary		Array which is used to build the sql query
+				* @since 3.1.2-RC1
+				*/
+				$vars = array('sql_ary');
+				extract($phpbb_dispatcher->trigger_event('core.acp_email_modify_sql', compact($vars)));
+
+				$sql = $db->sql_build_query('SELECT', $sql_ary);
 				$result = $db->sql_query($sql);
 				$row = $db->sql_fetchrow($result);
 
--- a/includes/acp/acp_extensions.php
+++ b/includes/acp/acp_extensions.php
@@ -537,7 +537,7 @@ class acp_extensions
 
 		$version_check = $meta['extra']['version-check'];
 
-		$version_helper = new \phpbb\version_helper($this->cache, $this->config, $this->user);
+		$version_helper = new \phpbb\version_helper($this->cache, $this->config, new \phpbb\file_downloader(), $this->user);
 		$version_helper->set_current_version($meta['version']);
 		$version_helper->set_file_location($version_check['host'], $version_check['directory'], $version_check['filename']);
 		$version_helper->force_stability($this->config['extension_force_unstable'] ? 'unstable' : null);
--- a/includes/acp/acp_styles.php
+++ b/includes/acp/acp_styles.php
@@ -29,14 +29,31 @@ class acp_styles
 	protected $styles_path;
 	protected $styles_path_absolute = 'styles';
 	protected $default_style = 0;
+	protected $styles_list_cols = 0;
+	protected $reserved_style_names = array('adm', 'admin', 'all');
 
+	/** @var \phpbb\db\driver\driver_interface */
 	protected $db;
+
+	/** @var \phpbb\user */
 	protected $user;
+
+	/** @var \phpbb\template\template */
 	protected $template;
+
+	/** @var \phpbb\request\request_interface */
 	protected $request;
+
+	/** @var \phpbb\cache\driver\driver_interface */
 	protected $cache;
+
+	/** @var \phpbb\auth\auth */
 	protected $auth;
+
+	/** @var string */
 	protected $phpbb_root_path;
+
+	/** @var string */
 	protected $php_ext;
 
 	public function main($id, $mode)
@@ -164,6 +181,12 @@ class acp_styles
 		$last_installed = false;
 		foreach ($dirs as $dir)
 		{
+			if (in_array($dir, $this->reserved_style_names))
+			{
+				$messages[] = $this->user->lang('STYLE_NAME_RESERVED', htmlspecialchars($dir));
+				continue;
+			}
+
 			$found = false;
 			foreach ($styles as &$style)
 			{
@@ -809,7 +832,7 @@ class acp_styles
 	* Update styles tree
 	*
 	* @param array $styles Styles list, passed as reference
-	* @param array $style Current style, false if root
+	* @param array|false $style Current style, false if root
 	* @return bool True if something was updated, false if not
 	*/
 	protected function update_styles_tree(&$styles, $style = false)
@@ -1091,7 +1114,7 @@ class acp_styles
 	/**
 	* Install style
 	*
-	* @param $style style data
+	* @param array $style style data
 	* @return int Style id
 	*/
 	protected function install_style($style)
--- a/includes/constants.php
+++ b/includes/constants.php
@@ -28,7 +28,7 @@ if (!defined('IN_PHPBB'))
 */
 
 // phpBB Version
-define('PHPBB_VERSION', '3.1.1');
+define('PHPBB_VERSION', '3.1.2');
 
 // QA-related
 // define('PHPBB_QA', 1);
--- a/includes/functions_admin.php
+++ b/includes/functions_admin.php
@@ -2512,6 +2512,7 @@ function phpbb_cache_moderators($db, $cache, $auth)
 			{
 				$usernames_ary[$row['user_id']] = $row['username'];
 			}
+			$db->sql_freeresult($result);
 
 			foreach ($hold_ary as $user_id => $forum_id_ary)
 			{
@@ -2806,6 +2807,7 @@ function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $li
 
 		$users[] = $row;
 	}
+	$db->sql_freeresult($result);
 
 	return $offset;
 }
@@ -2980,68 +2982,21 @@ function get_database_size()
 
 /**
 * Retrieve contents from remotely stored file
+*
+* @deprecated	3.1.2	Use file_downloader instead
 */
 function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port = 80, $timeout = 6)
 {
-	global $user;
+	global $phpbb_container;
 
-	if ($fsock = @fsockopen($host, $port, $errno, $errstr, $timeout))
-	{
-		@fputs($fsock, "GET $directory/$filename HTTP/1.0\r\n");
-		@fputs($fsock, "HOST: $host\r\n");
-		@fputs($fsock, "Connection: close\r\n\r\n");
+	// Get file downloader and assign $errstr and $errno
+	$file_downloader = $phpbb_container->get('file_downloader');
 
-		$timer_stop = time() + $timeout;
-		stream_set_timeout($fsock, $timeout);
-
-		$file_info = '';
-		$get_info = false;
-
-		while (!@feof($fsock))
-		{
-			if ($get_info)
-			{
-				$file_info .= @fread($fsock, 1024);
-			}
-			else
-			{
-				$line = @fgets($fsock, 1024);
-				if ($line == "\r\n")
-				{
-					$get_info = true;
-				}
-				else if (stripos($line, '404 not found') !== false)
-				{
-					$errstr = $user->lang('FILE_NOT_FOUND', $filename);
-					return false;
-				}
-			}
-
-			$stream_meta_data = stream_get_meta_data($fsock);
-
-			if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop)
-			{
-				$errstr = $user->lang['FSOCK_TIMEOUT'];
-				return false;
-			}
-		}
-		@fclose($fsock);
-	}
-	else
-	{
-		if ($errstr)
-		{
-			$errstr = utf8_convert_message($errstr);
-			return false;
-		}
-		else
-		{
-			$errstr = $user->lang['FSOCK_DISABLED'];
-			return false;
-		}
-	}
+	$file_data = $file_downloader->get($host, $directory, $filename, $port, $timeout);
+	$errstr = $file_downloader->get_error_string();
+	$errno = $file_downloader->get_error_number();
 
-	return $file_info;
+	return $file_data;
 }
 
 /*
--- a/includes/functions_content.php
+++ b/includes/functions_content.php
@@ -712,7 +712,7 @@ function make_clickable_callback($type, $whitespace, $url, $relative_url, $class
 		break;
 	}
 
-	$short_url = (strlen($url) > 55) ? substr($url, 0, 39) . ' ... ' . substr($url, -10) : $url;
+	$short_url = (utf8_strlen($url) > 55) ? utf8_substr($url, 0, 39) . ' ... ' . utf8_substr($url, -10) : $url;
 
 	switch ($type)
 	{
@@ -788,28 +788,28 @@ function make_clickable($text, $server_url = false, $class = 'postlink')
 
 		// relative urls for this board
 		$magic_url_match_args[$server_url][] = array(
-			'#(^|[\n\t (>.])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#i',
+			'#(^|[\n\t (>.])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#iu',
 			MAGIC_URL_LOCAL,
 			$local_class,
 		);
 
 		// matches a xxxx://aaaaa.bbb.cccc. ...
 		$magic_url_match_args[$server_url][] = array(
-			'#(^|[\n\t (>.])(' . get_preg_expression('url_inline') . ')#i',
+			'#(^|[\n\t (>.])(' . get_preg_expression('url_inline') . ')#iu',
 			MAGIC_URL_FULL,
 			$class,
 		);
 
 		// matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
 		$magic_url_match_args[$server_url][] = array(
-			'#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#i',
+			'#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#iu',
 			MAGIC_URL_WWW,
 			$class,
 		);
 
 		// matches an email@domain type address at the start of a line, or after a space or after what might be a BBCode.
 		$magic_url_match_args[$server_url][] = array(
-			'/(^|[\n\t (>])(' . get_preg_expression('email') . ')/i',
+			'/(^|[\n\t (>])(' . get_preg_expression('email') . ')/iu',
 			MAGIC_URL_EMAIL,
 			'',
 		);
--- a/includes/functions_display.php
+++ b/includes/functions_display.php
@@ -1608,8 +1608,8 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl
 		'U_EMAIL'		=> $email,
 		'U_JABBER'		=> ($data['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&amp;action=jabber&amp;u=' . $user_id) : '',
 
-		'USER_JABBER'		=> $data['user_jabber'],
-		'USER_JABBER_IMG'	=> ($data['user_jabber']) ? $user->img('icon_contact_jabber', $data['user_jabber']) : '',
+		'USER_JABBER'		=> ($config['jab_enable']) ? $data['user_jabber'] : '',
+		'USER_JABBER_IMG'	=> ($config['jab_enable'] && $data['user_jabber']) ? $user->img('icon_contact_jabber', $data['user_jabber']) : '',
 
 		'L_SEND_EMAIL_USER' => $user->lang('SEND_EMAIL_USER', $username),
 		'L_CONTACT_USER'	=> $user->lang('CONTACT_USER', $username),
--- a/includes/mcp/mcp_forum.php
+++ b/includes/mcp/mcp_forum.php
@@ -165,6 +165,22 @@ function mcp_forum_view($id, $mode, $action, $forum_info)
 			AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . "
 			$limit_time_sql
 		ORDER BY t.topic_type DESC, $sort_order_sql";
+
+	/**
+	* Modify SQL query before MCP forum view topic list is queried
+	*
+	* @event core.mcp_view_forum_modify_sql
+	* @var	string	sql			SQL query for forum view topic list
+	* @var	int	forum_id	ID of the forum
+	* @var	string  limit_time_sql		SQL query part for limit time
+	* @var	string  sort_order_sql		SQL query part for sort order
+	* @var	int topics_per_page			Number of topics per page
+	* @var	int start			Start value
+	* @since 3.1.2-RC1
+	*/
+	$vars = array('sql', 'forum_id', 'limit_time_sql', 'sort_order_sql', 'topics_per_page', 'start');
+	extract($phpbb_dispatcher->trigger_event('core.mcp_view_forum_modify_sql', compact($vars)));
+
 	$result = $db->sql_query_limit($sql, $topics_per_page, $start);
 
 	$topic_list = $topic_tracking_info = array();
--- a/includes/mcp/mcp_logs.php
+++ b/includes/mcp/mcp_logs.php
@@ -137,7 +137,7 @@ class mcp_logs
 
 					if ($mode == 'topic_logs')
 					{
-						$conditions['topic_logs'] = $topic_id;
+						$conditions['topic_id'] = $topic_id;
 					}
 
 					$phpbb_log->delete('mod', $conditions);
--- a/includes/mcp/mcp_main.php
+++ b/includes/mcp/mcp_main.php
@@ -813,8 +813,17 @@ function mcp_delete_topic($topic_ids, $is_soft = false, $soft_delete_reason = ''
 
 		$user->add_lang('posting');
 
+		// If there are only shadow topics, we neither need a reason nor softdelete
+		$sql = 'SELECT topic_id
+			FROM ' . TOPICS_TABLE . '
+			WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
+				AND topic_moved_id = 0';
+		$result = $db->sql_query_limit($sql, 1);
+		$only_shadow = !$db->sql_fetchfield('topic_id');
+		$db->sql_freeresult($result);
+
 		$only_softdeleted = false;
-		if ($auth->acl_get('m_delete', $forum_id) && $auth->acl_get('m_softdelete', $forum_id))
+		if (!$only_shadow && $auth->acl_get('m_delete', $forum_id) && $auth->acl_get('m_softdelete', $forum_id))
 		{
 			// If there are only soft deleted topics, we display a message why the option is not available
 			$sql = 'SELECT topic_id
@@ -827,6 +836,7 @@ function mcp_delete_topic($topic_ids, $is_soft = false, $soft_delete_reason = ''
 		}
 
 		$template->assign_vars(array(
+			'S_SHADOW_TOPICS'		=> $only_shadow,
 			'S_SOFTDELETED'			=> $only_softdeleted,
 			'S_TOPIC_MODE'			=> true,
 			'S_ALLOWED_DELETE'		=> $auth->acl_get('m_delete', $forum_id),
@@ -839,7 +849,7 @@ function mcp_delete_topic($topic_ids, $is_soft = false, $soft_delete_reason = ''
 			$l_confirm .= '_PERMANENTLY';
 			$s_hidden_fields['delete_permanent'] = '1';
 		}
-		else if (!$auth->acl_get('m_softdelete', $forum_id))
+		else if ($only_shadow || !$auth->acl_get('m_softdelete', $forum_id))
 		{
 			$s_hidden_fields['delete_permanent'] = '1';
 		}
--- a/includes/message_parser.php
+++ b/includes/message_parser.php
@@ -313,7 +313,7 @@ class bbcode_firstpass extends bbcode
 		$in = str_replace(' ', '%20', $in);
 
 		// Checking urls
-		if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#i', $in))
+		if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
 		{
 			return '[img]' . $in . '[/img]';
 		}
@@ -381,8 +381,8 @@ class bbcode_firstpass extends bbcode
 		$in = str_replace(' ', '%20', $in);
 
 		// Make sure $in is a URL.
-		if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) &&
-			!preg_match('#^' . get_preg_expression('www_url') . '$#i', $in))
+		if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) &&
+			!preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
 		{
 			return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
 		}
@@ -973,9 +973,9 @@ class bbcode_firstpass extends bbcode
 		$url = str_replace(' ', '%20', $url);
 
 		// Checking urls
-		if (preg_match('#^' . get_preg_expression('url') . '$#i', $url) ||
-			preg_match('#^' . get_preg_expression('www_url') . '$#i', $url) ||
-			preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#i', $url))
+		if (preg_match('#^' . get_preg_expression('url') . '$#iu', $url) ||
+			preg_match('#^' . get_preg_expression('www_url') . '$#iu', $url) ||
+			preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#iu', $url))
 		{
 			$valid = true;
 		}
@@ -1103,7 +1103,7 @@ class parse_message extends bbcode_firstpass
 	*/
 	function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post')
 	{
-		global $config, $db, $user;
+		global $config, $db, $user, $phpbb_dispatcher;
 
 		$this->mode = $mode;
 
@@ -1158,6 +1158,49 @@ class parse_message extends bbcode_firstpass
 			}
 		}
 
+		/**
+		* This event can be used for additional message checks/cleanup before parsing
+		*
+		* @event core.message_parser_check_message
+		* @var bool		allow_bbcode			Do we allow BBCodes
+		* @var bool		allow_magic_url			Do we allow magic urls
+		* @var bool		allow_smilies			Do we allow smilies
+		* @var bool		allow_img_bbcode		Do we allow image BBCode
+		* @var bool		allow_flash_bbcode		Do we allow flash BBCode
+		* @var bool		allow_quote_bbcode		Do we allow quote BBCode
+		* @var bool		allow_url_bbcode		Do we allow url BBCode
+		* @var bool		update_this_message		Do we alter the parsed message
+		* @var string	mode					Posting mode
+		* @var string	message					The message text to parse
+		* @var bool		return					Do we return after the event is triggered if $warn_msg is not empty
+		* @var array	warn_msg				Array of the warning messages
+		* @since 3.1.2-RC1
+		*/
+		$message = $this->message;
+		$warn_msg = $this->warn_msg;
+		$return = false;
+		$vars = array(
+			'allow_bbcode',
+			'allow_magic_url',
+			'allow_smilies',
+			'allow_img_bbcode',
+			'allow_flash_bbcode',
+			'allow_quote_bbcode',
+			'allow_url_bbcode',
+			'update_this_message',
+			'mode',
+			'message',
+			'return',
+			'warn_msg',
+		);
+		extract($phpbb_dispatcher->trigger_event('core.message_parser_check_message', compact($vars)));
+		$this->message = $message;
+		$this->warn_msg = $warn_msg;
+		if ($return && !empty($this->warn_msg))
+		{
+			return (!$update_this_message) ? $return_message : $this->warn_msg;
+		}
+
 		// Prepare BBcode (just prepares some tags for better parsing)
 		if ($allow_bbcode && strpos($this->message, '[') !== false)
 		{
--- a/includes/startup.php
+++ b/includes/startup.php
@@ -69,31 +69,13 @@ function deregister_globals()
 	{
 		if (isset($not_unset[$varname]))
 		{
-			// Hacking attempt. No point in continuing unless it's a COOKIE (so a cookie called GLOBALS doesn't lock users out completely)
-			if ($varname !== 'GLOBALS' || isset($_GET['GLOBALS']) || isset($_POST['GLOBALS']) || isset($_SERVER['GLOBALS']) || isset($_SESSION['GLOBALS']) || isset($_ENV['GLOBALS']) || isset($_FILES['GLOBALS']))
+			// Hacking attempt. No point in continuing.
+			if (isset($_COOKIE[$varname]))
 			{
-				exit;
-			}
-			else
-			{
-				$cookie = &$_COOKIE;
-				while (isset($cookie['GLOBALS']))
-				{
-					if (!is_array($cookie['GLOBALS']))
-					{
-						break;
-					}
-
-					foreach ($cookie['GLOBALS'] as $registered_var => $value)
-					{
-						if (!isset($not_unset[$registered_var]))
-						{
-							unset($GLOBALS[$registered_var]);
-						}
-					}
-					$cookie = &$cookie['GLOBALS'];
-				}
+				echo "Clear your cookies. ";
 			}
+			echo "Malicious variable name detected. Contact the administrator and ask them to disable register_globals.";
+			exit;
 		}
 
 		unset($GLOBALS[$varname]);
--- a/includes/ucp/ucp_login_link.php
+++ b/includes/ucp/ucp_login_link.php
@@ -75,7 +75,7 @@ class ucp_login_link
 		{
 			if ($request->is_set_post('login'))
 			{
-				$login_username = $request->variable('login_username', '', false, \phpbb\request\request_interface::POST);
+				$login_username = $request->variable('login_username', '', true, \phpbb\request\request_interface::POST);
 				$login_password = $request->untrimmed_variable('login_password', '', true, \phpbb\request\request_interface::POST);
 
 				$login_result = $auth_provider->login($login_username, $login_password);
--- a/includes/ucp/ucp_pm_viewmessage.php
+++ b/includes/ucp/ucp_pm_viewmessage.php
@@ -197,7 +197,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)
 		$u_pm = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=compose&amp;u=' . $author_id);
 	}
 
-	if ($user_info['user_jabber'] && $auth->acl_get('u_sendim'))
+	if ($config['jab_enable'] && $user_info['user_jabber'] && $auth->acl_get('u_sendim'))
 	{
 		$u_jabber = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&amp;action=jabber&amp;u=' . $author_id);
 	}
@@ -213,6 +213,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)
 		'AUTHOR_AVATAR'		=> (isset($user_info['avatar'])) ? $user_info['avatar'] : '',
 		'AUTHOR_JOINED'		=> $user->format_date($user_info['user_regdate']),
 		'AUTHOR_POSTS'		=> (int) $user_info['user_posts'],
+		'U_AUTHOR_POSTS'	=> ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$author_id&amp;sr=posts") : '',
 		'CONTACT_USER'		=> $user->lang('CONTACT_USER', get_username_string('username', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username'])),
 
 		'ONLINE_IMG'		=> (!$config['load_onlinetrack']) ? '' : ((isset($user_info['online']) && $user_info['online']) ? $user->img('icon_user_online', $user->lang['ONLINE']) : $user->img('icon_user_offline', $user->lang['OFFLINE'])),
--- a/includes/ucp/ucp_register.php
+++ b/includes/ucp/ucp_register.php
@@ -33,7 +33,8 @@ class ucp_register
 		global $request, $phpbb_container;
 
 		//
-		if ($config['require_activation'] == USER_ACTIVATION_DISABLE)
+		if ($config['require_activation'] == USER_ACTIVATION_DISABLE ||
+			(in_array($config['require_activation'], array(USER_ACTIVATION_SELF, USER_ACTIVATION_ADMIN)) && !$config['email_enable']))
 		{
 			trigger_error('UCP_REGISTER_DISABLE');
 		}
--- a/index.php
+++ b/index.php
@@ -185,7 +185,7 @@ $template->assign_vars(array(
 	'U_MCP'				=> ($auth->acl_get('m_') || $auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&amp;mode=front', true, $user->session_id) : '')
 );
 
-$page_title = $user->lang['INDEX'];
+$page_title = ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['INDEX'];
 
 /**
 * You can use this event to modify the page title and load data for the index
--- a/language/en/acp/board.php
+++ b/language/en/acp/board.php
@@ -221,7 +221,8 @@ $lang = array_merge($lang, array(
 	'ACP_REGISTER_SETTINGS_EXPLAIN'		=> 'Here you are able to define registration and profile related settings.',
 
 	'ACC_ACTIVATION'				=> 'Account activation',
-	'ACC_ACTIVATION_EXPLAIN'		=> 'This determines whether users have immediate access to the board or if confirmation is required. You can also completely disable new registrations. “Board-wide email” must be enabled in order to use user or admin activation.',
+	'ACC_ACTIVATION_EXPLAIN'		=> 'This determines whether users have immediate access to the board or if confirmation is required. You can also completely disable new registrations. <em>“Board-wide email” must be enabled in order to use user or admin activation.</em>',
+	'ACC_ACTIVATION_WARNING'		=> 'Please note that the currently selected activation method requires emails to be enabled, otherwise registration will be disabled. We recommend to either select a different activation method or reenable emails.',
 	'NEW_MEMBER_POST_LIMIT'			=> 'New member post limit',
 	'NEW_MEMBER_POST_LIMIT_EXPLAIN'	=> 'New members are within the <em>Newly Registered Users</em> group until they reach this number of posts. You can use this group to keep them from using the PM system or to review their posts. <strong>A value of 0 disables this feature.</strong>',
 	'NEW_MEMBER_GROUP_DEFAULT'		=> 'Set Newly Registered Users group to default',
@@ -556,7 +557,7 @@ $lang = array_merge($lang, array(
 	'EMAIL_SIG'						=> 'Email signature',
 	'EMAIL_SIG_EXPLAIN'				=> 'This text will be attached to all emails the board sends.',
 	'ENABLE_EMAIL'					=> 'Enable board-wide emails',
-	'ENABLE_EMAIL_EXPLAIN'			=> 'If this is set to disabled no emails will be sent by the board at all. <em>Note the user and admin account activation settings require this setting to be enabled. If currently using “user” or “admin” activation in the activation settings, disabling this setting will require no activation of new accounts.</em>',
+	'ENABLE_EMAIL_EXPLAIN'			=> 'If this is set to disabled no emails will be sent by the board at all. <em>Note the user and admin account activation settings require this setting to be enabled. If currently using “user” or “admin” activation in the activation settings, disabling this setting will disable registration.</em>',
 	'SMTP_AUTH_METHOD'				=> 'Authentication method for SMTP',
 	'SMTP_AUTH_METHOD_EXPLAIN'		=> 'Only used if a username/password is set, ask your provider if you are unsure which method to use.',
 	'SMTP_CRAM_MD5'					=> 'CRAM-MD5',
--- a/language/en/acp/styles.php
+++ b/language/en/acp/styles.php
@@ -74,6 +74,7 @@ $lang = array_merge($lang, array(
 	'STYLE_INSTALLED_RETURN_INSTALLED_STYLES'	=> 'Return to installed styles list',
 	'STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES'	=> 'Install more styles',
 	'STYLE_NAME'				=> 'Style name',
+	'STYLE_NAME_RESERVED'		=> 'Style "%s" can not be installed, because the name is reserved.',
 	'STYLE_NOT_INSTALLED'		=> 'Style "%s" was not installed.',
 	'STYLE_PATH'				=> 'Style path',
 	'STYLE_UNINSTALL'			=> 'Uninstall',
--- a/language/en/mcp.php
+++ b/language/en/mcp.php
@@ -237,7 +237,7 @@ $lang = array_merge($lang, array(
 	'NOT_MODERATOR'					=> 'You are not a moderator of this forum.',
 	'NO_DESTINATION_FORUM'			=> 'Please select a forum for destination.',
 	'NO_DESTINATION_FORUM_FOUND'	=> 'There is no destination forum available.',
-	'NO_ENTRIES'					=> 'No log entries for this period.',
+	'NO_ENTRIES'					=> 'No log entries.',
 	'NO_FEEDBACK'					=> 'No feedback exists for this user.',
 	'NO_FINAL_TOPIC_SELECTED'		=> 'You have to select a destination topic for merging posts.',
 	'NO_MATCHES_FOUND'				=> 'No matches found.',
--- a/language/en/memberlist.php
+++ b/language/en/memberlist.php
@@ -48,7 +48,7 @@ $lang = array_merge($lang, array(
 
 	'BEFORE'				=> 'Before',
 
-	'CC_EMAIL'				=> 'Send a copy of this email to yourself.',
+	'CC_SENDER'				=> 'Send a copy of this email to yourself.',
 	'CONTACT_ADMIN'			=> 'Contact a Board Administrator',
 
 	'DEST_LANG'				=> 'Language',
--- a/memberlist.php
+++ b/memberlist.php
@@ -681,7 +681,7 @@ switch ($mode)
 			'U_MCP_QUEUE'			=> ($auth->acl_getf_global('m_approve')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue', true, $user->session_id) : '',
 
 			'U_SWITCH_PERMISSIONS'	=> ($auth->acl_get('a_switchperm') && $user->data['user_id'] != $user_id) ? append_sid("{$phpbb_root_path}ucp.$phpEx", "mode=switch_perm&amp;u={$user_id}&amp;hash=" . generate_link_hash('switchperm')) : '',
-			'U_EDIT_SELF'			=> ($user_id == $user->data['user_id']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_profile&amp;mode=profile_info') : '',
+			'U_EDIT_SELF'			=> ($user_id == $user->data['user_id'] && $auth->acl_get('u_chgprofileinfo')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_profile&amp;mode=profile_info') : '',
 
 			'S_USER_NOTES'		=> ($user_notes_enabled) ? true : false,
 			'S_WARN_USER'		=> ($warn_user_enabled) ? true : false,
--- a/phpbb/auth/provider/apache.php
+++ b/phpbb/auth/provider/apache.php
@@ -137,7 +137,7 @@ class apache extends \phpbb\auth\provider\base
 			return array(
 				'status'		=> LOGIN_SUCCESS_CREATE_PROFILE,
 				'error_msg'		=> false,
-				'user_row'		=> user_row_apache($php_auth_user, $php_auth_pw),
+				'user_row'		=> $this->user_row($php_auth_user, $php_auth_pw),
 			);
 		}
 
@@ -185,7 +185,7 @@ class apache extends \phpbb\auth\provider\base
 			}
 
 			// create the user if he does not exist yet
-			user_add(user_row_apache($php_auth_user, $php_auth_pw));
+			user_add($this->user_row($php_auth_user, $php_auth_pw));
 
 			$sql = 'SELECT *
 				FROM ' . USERS_TABLE . "
--- a/phpbb/auth/provider/db.php
+++ b/phpbb/auth/provider/db.php
@@ -87,7 +87,7 @@ class db extends \phpbb\auth\provider\base
 
 		$username_clean = utf8_clean_string($username);
 
-		$sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type, user_login_attempts
+		$sql = 'SELECT *
 			FROM ' . USERS_TABLE . "
 			WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'";
 		$result = $this->db->sql_query($sql);
@@ -123,7 +123,7 @@ class db extends \phpbb\auth\provider\base
 				'username_clean'		=> $username_clean,
 			);
 			$sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $this->db->sql_build_array('INSERT', $attempt_data);
-			$result = $this->db->sql_query($sql);
+			$this->db->sql_query($sql);
 		}
 		else
 		{
@@ -175,7 +175,7 @@ class db extends \phpbb\auth\provider\base
 		}
 
 		// Check password ...
-		if ($this->passwords_manager->check($password, $row['user_password']))
+		if ($this->passwords_manager->check($password, $row['user_password'], $row))
 		{
 			// Check for old password hash...
 			if ($this->passwords_manager->convert_flag || strlen($row['user_password']) == 32)
@@ -232,7 +232,7 @@ class db extends \phpbb\auth\provider\base
 		// Give status about wrong password...
 		return array(
 			'status'		=> ($show_captcha) ? LOGIN_ERROR_ATTEMPTS : LOGIN_ERROR_PASSWORD,
-			'error_msg'		=> ($show_captcha) ? 'LOGIN_ERROR_ATTEMPTS' : 'LOGIN_ERROR_PASSWORD',
+			'error_msg'		=> 'LOGIN_ERROR_PASSWORD',
 			'user_row'		=> $row,
 		);
 	}
--- a/phpbb/avatar/manager.php
+++ b/phpbb/avatar/manager.php
@@ -326,17 +326,41 @@ class manager
 			$driver->delete($avatar_data);
 		}
 
-		$result = self::$default_row;
+		$result = $this->prefix_avatar_columns($prefix, self::$default_row);
 
-		foreach ($result as $key => $value)
+		$sql = 'UPDATE ' . $table . '
+			SET ' . $db->sql_build_array('UPDATE', $result) . '
+			WHERE ' . $prefix . 'id = ' . (int) $avatar_data['id'];
+		$db->sql_query($sql);
+
+		// Make sure we also delete this avatar from the users
+		if ($prefix === 'group_')
 		{
-			$result[$prefix . $key] = $value;
-			unset($result[$key]);
+			$result = $this->prefix_avatar_columns('user_', self::$default_row);
+
+			$sql = 'UPDATE ' . USERS_TABLE . '
+				SET ' . $db->sql_build_array('UPDATE', $result) . "
+				WHERE user_avatar = '" . $db->sql_escape($avatar_data['avatar']) . "'";
+			$db->sql_query($sql);
 		}
+	}
 
-		$sql = 'UPDATE ' . $table . '
-				SET ' . $db->sql_build_array('UPDATE', $result) . '
-				WHERE ' . $prefix . 'id = ' . (int) $avatar_data['id'];
-		$db->sql_query($sql);
+	/**
+	 * Prefix avatar columns
+	 *
+	 * @param string $prefix Column prefix
+	 * @param array $data Column data
+	 *
+	 * @return array Column data with prefixed column names
+	 */
+	public function prefix_avatar_columns($prefix, $data)
+	{
+		foreach ($data as $key => $value)
+		{
+			$data[$prefix . $key] = $value;
+			unset($data[$key]);
+		}
+
+		return $data;
 	}
 }
--- a/phpbb/captcha/plugins/recaptcha.php
+++ b/phpbb/captcha/plugins/recaptcha.php
@@ -26,8 +26,10 @@ class recaptcha extends captcha_abstract
 	var $challenge;
 	var $response;
 
-	// PHP4 Constructor
-	function phpbb_recaptcha()
+	/**
+	* Constructor
+	*/
+	public function __construct()
 	{
 		global $request;
 		$this->recaptcha_server = $request->is_secure() ? $this->recaptcha_server_secure : $this->recaptcha_server;
--- a/phpbb/controller/helper.php
+++ b/phpbb/controller/helper.php
@@ -44,6 +44,9 @@ class helper
 	/* @var \phpbb\symfony_request */
 	protected $symfony_request;
 
+	/* @var \phpbb\request\request_interface */
+	protected $request;
+
 	/**
 	* @var \phpbb\filesystem The filesystem object
 	*/
@@ -70,16 +73,18 @@ class helper
 	* @param \phpbb\controller\provider $provider Path provider
 	* @param \phpbb\extension\manager $manager Extension manager object
 	* @param \phpbb\symfony_request $symfony_request Symfony Request object
+	* @param \phpbb\request\request_interface $request phpBB request object
 	* @param \phpbb\filesystem $filesystem The filesystem object
 	* @param string $phpbb_root_path phpBB root path
 	* @param string $php_ext PHP file extension
 	*/
-	public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\controller\provider $provider, \phpbb\extension\manager $manager, \phpbb\symfony_request $symfony_request, \phpbb\filesystem $filesystem, $phpbb_root_path, $php_ext)
+	public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\controller\provider $provider, \phpbb\extension\manager $manager, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\filesystem $filesystem, $phpbb_root_path, $php_ext)
 	{
 		$this->template = $template;
 		$this->user = $user;
 		$this->config = $config;
 		$this->symfony_request = $symfony_request;
+		$this->request = $request;
 		$this->filesystem = $filesystem;
 		$this->phpbb_root_path = $phpbb_root_path;
 		$this->php_ext = $php_ext;
@@ -153,7 +158,7 @@ class helper
 			}
 		}
 
-		$base_url = $this->filesystem->clean_path($base_url);
+		$base_url = $this->request->escape($this->filesystem->clean_path($base_url), true);
 
 		$context->setBaseUrl($base_url);
 
@@ -197,6 +202,6 @@ class helper
 	*/
 	public function get_current_url()
 	{
-		return generate_board_url(true) . $this->symfony_request->getRequestUri();
+		return generate_board_url(true) . $this->request->escape($this->symfony_request->getRequestUri(), true);
 	}
 }
--- a/phpbb/db/migration/data/v310/captcha_plugins.php
+++ b/phpbb/db/migration/data/v310/captcha_plugins.php
@@ -25,9 +25,13 @@ class captcha_plugins extends \phpbb\db\migration\migration
 	public function update_data()
 	{
 		$captcha_plugin = $this->config['captcha_plugin'];
-		if (strpos($this->config['captcha_plugin'], 'phpbb_captcha_') === 0)
+		if (strpos($captcha_plugin, 'phpbb_captcha_') === 0)
 		{
-			$captcha_plugin = substr($this->config['captcha_plugin'], strlen('phpbb_captcha_'));
+			$captcha_plugin = substr($captcha_plugin, strlen('phpbb_captcha_'));
+		}
+		else if (strpos($captcha_plugin, 'phpbb_') === 0)
+		{
+			$captcha_plugin = substr($captcha_plugin, strlen('phpbb_'));
 		}
 
 		return array(
--- a/phpbb/db/migration/data/v310/reset_missing_captcha_plugin.php
+++ b/phpbb/db/migration/data/v310/reset_missing_captcha_plugin.php
@@ -29,7 +29,8 @@ class reset_missing_captcha_plugin extends \phpbb\db\migration\migration
 	{
 		return array(
 			array('if', array(
-				(!is_file($this->phpbb_root_path . "includes/captcha/plugins/{$this->config['captcha_plugin']}_plugin." . $this->php_ext)),
+				(is_dir($this->phpbb_root_path . 'includes/captcha/plugins/') &&
+				!is_file($this->phpbb_root_path . "includes/captcha/plugins/{$this->config['captcha_plugin']}_plugin." . $this->php_ext)),
 				array('config.update', array('captcha_plugin', 'phpbb_captcha_nogd')),
 			)),
 		);
--- /dev/null
+++ b/phpbb/db/migration/data/v31x/m_softdelete_global.php
@@ -0,0 +1,31 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\db\migration\data\v31x;
+
+class m_softdelete_global extends \phpbb\db\migration\migration
+{
+	static public function depends_on()
+	{
+		return array('\phpbb\db\migration\data\v31x\v311');
+	}
+
+	public function update_data()
+	{
+		return array(
+			// Make m_softdelete global. The add method will take care of updating
+			// it if it already exists.
+			array('permission.add', array('m_softdelete', true)),
+		);
+	}
+}
--- /dev/null
+++ b/phpbb/db/migration/data/v31x/v312.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\db\migration\data\v31x;
+
+class v312 extends \phpbb\db\migration\migration
+{
+	static public function depends_on()
+	{
+		return array(
+			'\phpbb\db\migration\data\v31x\v312rc1',
+		);
+	}
+
+	public function update_data()
+	{
+		return array(
+			array('config.update', array('version', '3.1.2')),
+		);
+	}
+}
--- /dev/null
+++ b/phpbb/db/migration/data/v31x/v312rc1.php
@@ -0,0 +1,32 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\db\migration\data\v31x;
+
+class v312rc1 extends \phpbb\db\migration\migration
+{
+	static public function depends_on()
+	{
+		return array(
+			'\phpbb\db\migration\data\v31x\v311',
+			'\phpbb\db\migration\data\v31x\m_softdelete_global',
+		);
+	}
+
+	public function update_data()
+	{
+		return array(
+			array('config.update', array('version', '3.1.2-RC1')),
+		);
+	}
+}
--- a/phpbb/db/migrator.php
+++ b/phpbb/db/migrator.php
@@ -59,6 +59,13 @@ class migrator
 	protected $migrations = array();
 
 	/**
+	* Array of migrations that have been determined to be fulfillable
+	*
+	* @var array
+	*/
+	protected $fulfillable_migrations = array();
+
+	/**
 	* 'name,' 'class,' and 'state' of the last migration run
 	*
 	* 'effectively_installed' set and set to true if the migration was effectively_installed
@@ -653,7 +660,7 @@ class migrator
 	*/
 	public function unfulfillable($name)
 	{
-		if (isset($this->migration_state[$name]))
+		if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name]))
 		{
 			return false;
 		}
@@ -674,6 +681,7 @@ class migrator
 				return $unfulfillable;
 			}
 		}
+		$this->fulfillable_migrations[$name] = true;
 
 		return false;
 	}
--- a/phpbb/di/extension/config.php
+++ b/phpbb/di/extension/config.php
@@ -39,16 +39,24 @@ class config extends Extension
 	*/
 	public function load(array $config, ContainerBuilder $container)
 	{
-		$container->setParameter('core.adm_relative_path', ($this->config_php->get('phpbb_adm_relative_path') ? $this->config_php->get('phpbb_adm_relative_path') : 'adm/'));
-		$container->setParameter('core.table_prefix', $this->config_php->get('table_prefix'));
-		$container->setParameter('cache.driver.class', $this->convert_30_acm_type($this->config_php->get('acm_type')));
-		$container->setParameter('dbal.driver.class', $this->config_php->convert_30_dbms_to_31($this->config_php->get('dbms')));
-		$container->setParameter('dbal.dbhost', $this->config_php->get('dbhost'));
-		$container->setParameter('dbal.dbuser', $this->config_php->get('dbuser'));
-		$container->setParameter('dbal.dbpasswd', $this->config_php->get('dbpasswd'));
-		$container->setParameter('dbal.dbname', $this->config_php->get('dbname'));
-		$container->setParameter('dbal.dbport', $this->config_php->get('dbport'));
-		$container->setParameter('dbal.new_link', defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK);
+		$parameters = array(
+			'core.adm_relative_path'	=> $this->config_php->get('phpbb_adm_relative_path') ? $this->config_php->get('phpbb_adm_relative_path') : 'adm/',
+			'core.table_prefix'			=> $this->config_php->get('table_prefix'),
+			'cache.driver.class'		=> $this->convert_30_acm_type($this->config_php->get('acm_type')),
+			'dbal.driver.class'			=> $this->config_php->convert_30_dbms_to_31($this->config_php->get('dbms')),
+			'dbal.dbhost'				=> $this->config_php->get('dbhost'),
+			'dbal.dbuser'				=> $this->config_php->get('dbuser'),
+			'dbal.dbpasswd'				=> $this->config_php->get('dbpasswd'),
+			'dbal.dbname'				=> $this->config_php->get('dbname'),
+			'dbal.dbport'				=> $this->config_php->get('dbport'),
+			'dbal.new_link'				=> defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK,
+		);
+		$parameter_bag = $container->getParameterBag();
+
+		foreach ($parameters as $parameter => $value)
+		{
+			$container->setParameter($parameter, $parameter_bag->escapeValue($value));
+		}
 	}
 
 	/**
--- a/phpbb/extension/metadata_manager.php
+++ b/phpbb/extension/metadata_manager.php
@@ -177,6 +177,7 @@ class metadata_manager
 				throw new \phpbb\extension\exception($this->user->lang('FILE_JSON_DECODE_ERR', $this->metadata_file));
 			}
 
+			array_walk_recursive($metadata, array($this, 'sanitize_json'));
 			$this->metadata = $metadata;
 
 			return true;
@@ -184,6 +185,17 @@ class metadata_manager
 	}
 
 	/**
+	 * Sanitize input from JSON array using htmlspecialchars()
+	 *
+	 * @param mixed		$value	Value of array row
+	 * @param string	$key	Key of array row
+	 */
+	public function sanitize_json(&$value, $key)
+	{
+		$value = htmlspecialchars($value);
+	}
+
+	/**
 	* This array handles the cleaning of the array
 	*
 	* @return array Contains the cleaned metadata array
@@ -337,30 +349,30 @@ class metadata_manager
 	public function output_template_data()
 	{
 		$this->template->assign_vars(array(
-			'META_NAME'			=> htmlspecialchars($this->metadata['name']),
-			'META_TYPE'			=> htmlspecialchars($this->metadata['type']),
-			'META_DESCRIPTION'	=> (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '',
+			'META_NAME'			=> $this->metadata['name'],
+			'META_TYPE'			=> $this->metadata['type'],
+			'META_DESCRIPTION'	=> (isset($this->metadata['description'])) ? $this->metadata['description'] : '',
 			'META_HOMEPAGE'		=> (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '',
-			'META_VERSION'		=> (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '',
-			'META_TIME'			=> (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '',
-			'META_LICENSE'		=> htmlspecialchars($this->metadata['license']),
+			'META_VERSION'		=> (isset($this->metadata['version'])) ? $this->metadata['version'] : '',
+			'META_TIME'			=> (isset($this->metadata['time'])) ? $this->metadata['time'] : '',
+			'META_LICENSE'		=> $this->metadata['license'],
 
-			'META_REQUIRE_PHP'		=> (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '',
+			'META_REQUIRE_PHP'		=> (isset($this->metadata['require']['php'])) ? $this->metadata['require']['php'] : '',
 			'META_REQUIRE_PHP_FAIL'	=> !$this->validate_require_php(),
 
-			'META_REQUIRE_PHPBB'		=> (isset($this->metadata['extra']['soft-require']['phpbb/phpbb'])) ? htmlspecialchars($this->metadata['extra']['soft-require']['phpbb/phpbb']) : '',
+			'META_REQUIRE_PHPBB'		=> (isset($this->metadata['extra']['soft-require']['phpbb/phpbb'])) ? $this->metadata['extra']['soft-require']['phpbb/phpbb'] : '',
 			'META_REQUIRE_PHPBB_FAIL'	=> !$this->validate_require_phpbb(),
 
-			'META_DISPLAY_NAME'	=> (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '',
+			'META_DISPLAY_NAME'	=> (isset($this->metadata['extra']['display-name'])) ? $this->metadata['extra']['display-name'] : '',
 		));
 
 		foreach ($this->metadata['authors'] as $author)
 		{
 			$this->template->assign_block_vars('meta_authors', array(
-				'AUTHOR_NAME'		=> htmlspecialchars($author['name']),
+				'AUTHOR_NAME'		=> $author['name'],
 				'AUTHOR_EMAIL'		=> (isset($author['email'])) ? $author['email'] : '',
 				'AUTHOR_HOMEPAGE'	=> (isset($author['homepage'])) ? $author['homepage'] : '',
-				'AUTHOR_ROLE'		=> (isset($author['role'])) ? htmlspecialchars($author['role']) : '',
+				'AUTHOR_ROLE'		=> (isset($author['role'])) ? $author['role'] : '',
 			));
 		}
 	}
--- /dev/null
+++ b/phpbb/file_downloader.php
@@ -0,0 +1,120 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb;
+
+class file_downloader
+{
+	/** @var string Error string */
+	protected $error_string = '';
+
+	/** @var int Error number */
+	protected $error_number = 0;
+
+	/**
+	 * Retrieve contents from remotely stored file
+	 *
+	 * @param string	$host			File host
+	 * @param string	$directory		Directory file is in
+	 * @param string	$filename		Filename of file to retrieve
+	 * @param int		$port			Port to connect to; default: 80
+	 * @param int		$timeout		Connection timeout in seconds; default: 6
+	 *
+	 * @return mixed File data as string if file can be read and there is no
+	 *			timeout, false if there were errors or the connection timed out
+	 *
+	 * @throws \RuntimeException If data can't be retrieved and no error
+	 *		message is returned
+	 */
+	public function get($host, $directory, $filename, $port = 80, $timeout = 6)
+	{
+		// Set default values for error variables
+		$this->error_number = 0;
+		$this->error_string = '';
+
+		if ($socket = @fsockopen($host, $port, $this->error_number, $this->error_string, $timeout))
+		{
+			@fputs($socket, "GET $directory/$filename HTTP/1.0\r\n");
+			@fputs($socket, "HOST: $host\r\n");
+			@fputs($socket, "Connection: close\r\n\r\n");
+
+			$timer_stop = time() + $timeout;
+			stream_set_timeout($socket, $timeout);
+
+			$file_info = '';
+			$get_info = false;
+
+			while (!@feof($socket))
+			{
+				if ($get_info)
+				{
+					$file_info .= @fread($socket, 1024);
+				}
+				else
+				{
+					$line = @fgets($socket, 1024);
+					if ($line == "\r\n")
+					{
+						$get_info = true;
+					}
+					else if (stripos($line, '404 not found') !== false)
+					{
+						throw new \RuntimeException(array('FILE_NOT_FOUND', $filename));
+					}
+				}
+
+				$stream_meta_data = stream_get_meta_data($socket);
+
+				if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop)
+				{
+					throw new \RuntimeException('FSOCK_TIMEOUT');
+				}
+			}
+			@fclose($socket);
+		}
+		else
+		{
+			if ($this->error_string)
+			{
+				$this->error_string = utf8_convert_message($this->error_string);
+				return false;
+			}
+			else
+			{
+				throw new \RuntimeException('FSOCK_DISABLED');
+			}
+		}
+
+		return $file_info;
+	}
+
+	/**
+	 * Get error string
+	 *
+	 * @return string Error string
+	 */
+	public function get_error_string()
+	{
+		return $this->error_string;
+	}
+
+	/**
+	 * Get error number
+	 *
+	 * @return int Error number
+	 */
+	public function get_error_number()
+	{
+		return $this->error_number;
+	}
+}
--- a/phpbb/message/admin_form.php
+++ b/phpbb/message/admin_form.php
@@ -178,6 +178,7 @@ class admin_form extends form
 			'S_CONTACT_ADMIN'	=> true,
 			'S_CONTACT_FORM'	=> $this->config['contact_admin_form_enable'],
 			'S_IS_REGISTERED'	=> $this->user->data['is_registered'],
+			'S_POST_ACTION'		=> append_sid($this->phpbb_root_path . 'memberlist.' . $this->phpEx, 'mode=contactadmin'),
 
 			'CONTACT_INFO'		=> $l_admin_info,
 			'MESSAGE'			=> $this->body,
--- a/phpbb/message/topic_form.php
+++ b/phpbb/message/topic_form.php
@@ -117,7 +117,7 @@ class topic_form extends form
 			'TOPIC_NAME'	=> htmlspecialchars_decode($this->topic_row['topic_title']),
 			'U_TOPIC'		=> generate_board_url() . '/viewtopic.' . $this->phpEx . '?f=' . $this->topic_row['forum_id'] . '&t=' . $this->topic_id,
 		));
-
+		$this->message->set_body($this->body);
 		$this->message->add_recipient(
 			$this->recipient_name,
 			$this->recipient_address,
--- a/phpbb/notification/manager.php
+++ b/phpbb/notification/manager.php
@@ -24,6 +24,9 @@ class manager
 	protected $notification_types;
 
 	/** @var array */
+	protected $subscription_types;
+
+	/** @var array */
 	protected $notification_methods;
 
 	/** @var ContainerInterface */
@@ -289,7 +292,7 @@ class manager
 			WHERE notification_time <= " . (int) $time .
 				(($notification_type_name !== false) ? ' AND ' .
 					(is_array($notification_type_name) ? $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : 'notification_type_id = ' . $this->get_notification_type_id($notification_type_name)) : '') .
-				(($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id) : 'item_parent_id = ' . (int) $item_parent_id) : '') .
+				(($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : 'item_parent_id = ' . (int) $item_parent_id) : '') .
 				(($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '');
 		$this->db->sql_query($sql);
 	}
@@ -524,33 +527,36 @@ class manager
 	*/
 	public function get_subscription_types()
 	{
-		$subscription_types = array();
-
-		foreach ($this->notification_types as $type_name => $data)
+		if ($this->subscription_types === null)
 		{
-			$type = $this->get_item_type_class($type_name);
+			$this->subscription_types = array();
 
-			if ($type instanceof \phpbb\notification\type\type_interface && $type->is_available())
+			foreach ($this->notification_types as $type_name => $data)
 			{
-				$options = array_merge(array(
-					'id'		=> $type->get_type(),
-					'lang'		=> 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()),
-					'group'		=> 'NOTIFICATION_GROUP_MISCELLANEOUS',
-				), (($type::$notification_option !== false) ? $type::$notification_option : array()));
+				$type = $this->get_item_type_class($type_name);
+
+				if ($type instanceof \phpbb\notification\type\type_interface && $type->is_available())
+				{
+					$options = array_merge(array(
+						'id' => $type->get_type(),
+						'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()),
+						'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS',
+					), (($type::$notification_option !== false) ? $type::$notification_option : array()));
 
-				$subscription_types[$options['group']][$options['id']] = $options;
+					$this->subscription_types[$options['group']][$options['id']] = $options;
+				}
 			}
-		}
 
-		// Move Miscellaneous to the very last section
-		if (isset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']))
-		{
-			$miscellaneous = $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'];
-			unset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']);
-			$subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous;
+			// Move Miscellaneous to the very last section
+			if (isset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']))
+			{
+				$miscellaneous = $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'];
+				unset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']);
+				$this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous;
+			}
 		}
 
-		return $subscription_types;
+		return $this->subscription_types;
 	}
 
 	/**
--- a/phpbb/path_helper.php
+++ b/phpbb/path_helper.php
@@ -154,6 +154,7 @@ class path_helper
 			return $this->web_root_path;
 		}
 
+		// We do not need to escape $path_info, $request_uri and $script_name because we can not find their content in the result.
 		// Path info (e.g. /foo/bar)
 		$path_info = $this->filesystem->clean_path($this->symfony_request->getPathInfo());
 
@@ -203,9 +204,12 @@ class path_helper
 		*/
 		if ($this->request->is_ajax() && $this->symfony_request->get('_referer'))
 		{
+			// We need to escape $absolute_board_url because it can be partially concatenated to the result.
+			$absolute_board_url = $this->request->escape($this->symfony_request->getSchemeAndHttpHost() . $this->symfony_request->getBasePath(), true);
+
 			$referer_web_root_path = $this->get_web_root_path_from_ajax_referer(
 				$this->symfony_request->get('_referer'),
-				$this->symfony_request->getSchemeAndHttpHost() . $this->symfony_request->getBasePath()
+				$absolute_board_url
 			);
 			return $this->web_root_path = $this->phpbb_root_path . $referer_web_root_path;
 		}
@@ -278,10 +282,16 @@ class path_helper
 			$referer_dir = dirname($referer_dir);
 		}
 
-		while (strpos($absolute_board_url, $referer_dir) !== 0)
+		while (($dir_position = strpos($absolute_board_url, $referer_dir)) !== 0)
 		{
 			$fixed_root_path .= '../';
 			$referer_dir = dirname($referer_dir);
+
+			// Just return phpbb_root_path if we reach the top directory
+			if ($referer_dir === '.')
+			{
+				return $this->phpbb_root_path;
+			}
 		}
 
 		$fixed_root_path .= substr($absolute_board_url, strlen($referer_dir) + 1);
--- a/phpbb/profilefields/type/type_base.php
+++ b/phpbb/profilefields/type/type_base.php
@@ -158,7 +158,19 @@ abstract class type_base implements type_interface
 		}
 		else
 		{
-			return $this->request->variable($key, '', true);
+			$default_value = '';
+			$lang_fields = array(
+				'l_lang_name',
+				'l_lang_explain',
+				'l_lang_default_value',
+				'l_lang_options',
+			);
+
+			if (in_array($key, $lang_fields))
+			{
+				$default_value = array(0 => '');
+			}
+			return $this->request->variable($key, $default_value, true);
 		}
 	}
 
--- a/phpbb/profilefields/type/type_bool.php
+++ b/phpbb/profilefields/type/type_bool.php
@@ -352,7 +352,7 @@ class type_bool extends type_base
 			}
 		}
 
-		if ($step == 3 && ($field_data[$key] || $action != 'edit') && $key == 'l_lang_options')
+		if ($key == 'l_lang_options' && $this->request->is_set($key))
 		{
 			$field_data[$key] = $this->request->variable($key, array(0 => array('')), true);
 
--- a/phpbb/request/request.php
+++ b/phpbb/request/request.php
@@ -416,4 +416,27 @@ class request implements \phpbb\request\request_interface
 	{
 		return $this->input[$super_global];
 	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function escape($var, $multibyte)
+	{
+		if (is_array($var))
+		{
+			$result = array();
+			foreach ($var as $key => $value)
+			{
+				$this->type_cast_helper->set_var($key, $key, gettype($key), $multibyte);
+				$result[$key] = $this->escape($value, $multibyte);
+			}
+			$var = $result;
+		}
+		else
+		{
+			$this->type_cast_helper->set_var($var, $var, 'string', $multibyte);
+		}
+
+		return $var;
+	}
 }
--- a/phpbb/request/request_interface.php
+++ b/phpbb/request/request_interface.php
@@ -142,4 +142,14 @@ interface request_interface
 	* @return	array	The original array of the requested super global.
 	*/
 	public function get_super_global($super_global = \phpbb\request\request_interface::REQUEST);
+
+	/**
+	 * Escape a string variable.
+	 *
+	 * @param mixed	$value		The contents to fill with
+	 * @param bool	$multibyte	Indicates whether string values may contain UTF-8 characters.
+	 * 							Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks.
+	 * @return string|array
+	 */
+	public function escape($value, $multibyte);
 }
--- a/phpbb/session.php
+++ b/phpbb/session.php
@@ -31,10 +31,11 @@ class session
 	var $update_session_page = true;
 
 	/**
-	* Extract current session page
-	*
-	* @param string $root_path current root path (phpbb_root_path)
-	*/
+	 * Extract current session page
+	 *
+	 * @param string $root_path current root path (phpbb_root_path)
+	 * @return array
+	 */
 	static function extract_current_page($root_path)
 	{
 		global $request, $symfony_request, $phpbb_filesystem;
@@ -42,8 +43,8 @@ class session
 		$page_array = array();
 
 		// First of all, get the request uri...
-		$script_name = $symfony_request->getScriptName();
-		$args = explode('&', $symfony_request->getQueryString());
+		$script_name = $request->escape($symfony_request->getScriptName(), true);
+		$args = $request->escape(explode('&', $symfony_request->getQueryString()), true);
 
 		// If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support...
 		if (!$script_name)
@@ -61,8 +62,8 @@ class session
 
 		// Since some browser do not encode correctly we need to do this with some "special" characters...
 		// " -> %22, ' => %27, < -> %3C, > -> %3E
-		$find = array('"', "'", '<', '>');
-		$replace = array('%22', '%27', '%3C', '%3E');
+		$find = array('"', "'", '<', '>', '&quot;', '&lt;', '&gt;');
+		$replace = array('%22', '%27', '%3C', '%3E', '%22', '%3C', '%3E');
 
 		foreach ($args as $key => $argument)
 		{
--- a/phpbb/symfony_request.php
+++ b/phpbb/symfony_request.php
@@ -15,6 +15,10 @@ namespace phpbb;
 
 use Symfony\Component\HttpFoundation\Request;
 
+/**
+ * WARNING: The Symfony request does not escape the input and should be used very carefully
+ * prefer the phpbb request as possible
+ */
 class symfony_request extends Request
 {
 	/**
@@ -24,24 +28,12 @@ class symfony_request extends Request
 	*/
 	public function __construct(\phpbb\request\request_interface $phpbb_request)
 	{
-		// This function is meant to sanitize the global input arrays
-		$sanitizer = function(&$value, $key) {
-			$type_cast_helper = new \phpbb\request\type_cast_helper();
-			$type_cast_helper->set_var($value, $value, gettype($value), true);
-		};
-
 		$get_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::GET);
 		$post_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::POST);
 		$server_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::SERVER);
 		$files_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::FILES);
 		$cookie_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::COOKIE);
 
-		array_walk_recursive($get_parameters, $sanitizer);
-		array_walk_recursive($post_parameters, $sanitizer);
-		array_walk_recursive($server_parameters, $sanitizer);
-		array_walk_recursive($files_parameters, $sanitizer);
-		array_walk_recursive($cookie_parameters, $sanitizer);
-
 		parent::__construct($get_parameters, $post_parameters, array(), $cookie_parameters, $files_parameters, $server_parameters);
 	}
 }
--- a/phpbb/template/twig/twig.php
+++ b/phpbb/template/twig/twig.php
@@ -189,13 +189,24 @@ class twig extends \phpbb\template\base
 			{
 				$path = $this->phpbb_root_path . trim($directory, '/') . "/{$name}/";
 				$template_path = $path . 'template/';
+				$theme_path = $path . 'theme/';
 
+				$is_valid_dir = false;
 				if (is_dir($template_path))
 				{
+					$is_valid_dir = true;
+					$paths[] = $template_path;
+				}
+				if (is_dir($theme_path))
+				{
+					$is_valid_dir = true;
+					$paths[] = $theme_path;
+				}
+
+				if ($is_valid_dir)
+				{
 					// Add the base style directory as a safe directory
 					$this->twig->getLoader()->addSafeDirectory($path);
-
-					$paths[] = $template_path;
 				}
 			}
 		}
@@ -253,25 +264,38 @@ class twig extends \phpbb\template\base
 						{
 							$ext_style_template_path = $ext_path . $template_dir['ext_path'];
 							$ext_style_path = dirname($ext_style_template_path);
+							$ext_style_theme_path = $ext_style_path . 'theme/';
 						}
 						else
 						{
 							$ext_style_path = $ext_path . 'styles/' . $template_dir['name'] . '/';
 							$ext_style_template_path = $ext_style_path . 'template/';
+							$ext_style_theme_path = $ext_style_path . 'theme/';
 						}
 					}
 					else
 					{
 						$ext_style_path = $ext_path . 'styles/' . $template_dir . '/';
 						$ext_style_template_path = $ext_style_path . 'template/';
+						$ext_style_theme_path = $ext_style_path . 'theme/';
 					}
 
+					$ok = false;
 					if (is_dir($ext_style_template_path))
 					{
+						$ok = true;
+						$paths[] = $ext_style_template_path;
+					}
+					if (is_dir($ext_style_theme_path))
+					{
+						$ok = true;
+						$paths[] = $ext_style_theme_path;
+					}
+
+					if ($ok)
+					{
 						// Add the base style directory as a safe directory
 						$this->twig->getLoader()->addSafeDirectory($ext_style_path);
-
-						$paths[] = $ext_style_template_path;
 					}
 				}
 
--- a/phpbb/version_helper.php
+++ b/phpbb/version_helper.php
@@ -50,6 +50,9 @@ class version_helper
 	/** @var \phpbb\config\config */
 	protected $config;
 
+	/** @var \phpbb\file_downloader */
+	protected $file_downloader;
+
 	/** @var \phpbb\user */
 	protected $user;
 
@@ -58,12 +61,14 @@ class version_helper
 	 *
 	 * @param \phpbb\cache\service $cache
 	 * @param \phpbb\config\config $config
+	 * @param \phpbb\file_downloader $file_downloader
 	 * @param \phpbb\user $user
 	 */
-	public function __construct(\phpbb\cache\service $cache, \phpbb\config\config $config, \phpbb\user $user)
+	public function __construct(\phpbb\cache\service $cache, \phpbb\config\config $config, \phpbb\file_downloader $file_downloader, \phpbb\user $user)
 	{
 		$this->cache = $cache;
 		$this->config = $config;
+		$this->file_downloader = $file_downloader;
 		$this->user = $user;
 
 		if (defined('PHPBB_QA'))
@@ -249,16 +254,32 @@ class version_helper
 		}
 		else if ($info === false || $force_update)
 		{
-			$errstr = $errno = '';
-			$info = get_remote_file($this->host, $this->path, $this->file, $errstr, $errno);
+			try {
+				$info = $this->file_downloader->get($this->host, $this->path, $this->file);
+			}
+			catch (\RuntimeException $exception)
+			{
+				throw new \RuntimeException(call_user_func_array(array($this->user, 'lang'), $exception->getMessage()));
+			}
+			$error_string = $this->file_downloader->get_error_string();
 
-			if (!empty($errstr))
+			if (!empty($error_string))
 			{
-				throw new \RuntimeException($errstr);
+				throw new \RuntimeException($error_string);
 			}
 
 			$info = json_decode($info, true);
 
+			// Sanitize any data we retrieve from a server
+			if (!empty($info))
+			{
+				$json_sanitizer = function (&$value, $key) {
+					$type_cast_helper = new \phpbb\request\type_cast_helper();
+					$type_cast_helper->set_var($value, $value, gettype($value), true);
+				};
+				array_walk_recursive($info, $json_sanitizer);
+			}
+
 			if (empty($info['stable']) && empty($info['unstable']))
 			{
 				$this->user->add_lang('acp/common');
@@ -266,15 +287,6 @@ class version_helper
 				throw new \RuntimeException($this->user->lang('VERSIONCHECK_FAIL'));
 			}
 
-			// Replace & with &amp; on announcement links
-			foreach ($info as $stability => $branches)
-			{
-				foreach ($branches as $branch => $branch_data)
-				{
-					$info[$stability][$branch]['announcement'] = str_replace('&', '&amp;', $branch_data['announcement']);
-				}
-			}
-
 			$info['stable'] = (empty($info['stable'])) ? array() : $info['stable'];
 			$info['unstable'] = (empty($info['unstable'])) ? $info['stable'] : $info['unstable'];
 
--- a/posting.php
+++ b/posting.php
@@ -73,7 +73,6 @@ $current_time = time();
 * @var	bool	preview		Whether or not the post is being previewed
 * @var	bool	save		Whether or not a draft is being saved
 * @var	bool	load		Whether or not a draft is being loaded
-* @var	bool	delete		Whether or not the post is being deleted
 * @var	bool	cancel		Whether or not to cancel the form (returns to
 *							viewtopic or viewforum depending on if the user
 *							is posting a new topic or editing a post)
@@ -85,6 +84,7 @@ $current_time = time();
 *							NOTE: Should be actual language strings, NOT
 *							language keys.
 * @since 3.1.0-a1
+* @change 3.1.2-RC1			Removed 'delete' var as it does not exist
 */
 $vars = array(
 	'post_id',
@@ -96,7 +96,6 @@ $vars = array(
 	'preview',
 	'save',
 	'load',
-	'delete',
 	'cancel',
 	'refresh',
 	'mode',
@@ -870,6 +869,43 @@ if ($submit || $preview || $refresh)
 	// Parse Attachments - before checksum is calculated
 	$message_parser->parse_attachments('fileupload', $mode, $forum_id, $submit, $preview, $refresh);
 
+	/**
+	* This event allows you to modify message text before parsing
+	*
+	* @event core.posting_modify_message_text
+	* @var	array	post_data	Array with post data
+	* @var	string	mode		What action to take if the form is submitted
+	*				post|reply|quote|edit|delete|bump|smilies|popup
+	* @var	int	post_id		ID of the post
+	* @var	int	topic_id	ID of the topic
+	* @var	int	forum_id	ID of the forum
+	* @var	bool	submit		Whether or not the form has been submitted
+	* @var	bool	preview		Whether or not the post is being previewed
+	* @var	bool	save		Whether or not a draft is being saved
+	* @var	bool	load		Whether or not a draft is being loaded
+	* @var	bool	cancel		Whether or not to cancel the form (returns to
+	*				viewtopic or viewforum depending on if the user
+	*				is posting a new topic or editing a post)
+	* @var	bool	refresh		Whether or not to retain previously submitted data
+	* @var	object	message_parser	The message parser object
+	* @since 3.1.2-RC1
+	*/
+	$vars = array(
+		'post_data',
+		'mode',
+		'post_id',
+		'topic_id',
+		'forum_id',
+		'submit',
+		'preview',
+		'save',
+		'load',
+		'cancel',
+		'refresh',
+		'message_parser',
+	);
+	extract($phpbb_dispatcher->trigger_event('core.posting_modify_message_text', compact($vars)));
+
 	// Grab md5 'checksum' of new message
 	$message_md5 = md5($message_parser->message);
 
@@ -1125,7 +1161,7 @@ if ($submit || $preview || $refresh)
 			break;
 		}
 
-		if (!$auth->acl_get($auth_option, $forum_id))
+		if ($auth_option != '' && !$auth->acl_get($auth_option, $forum_id))
 		{
 			// There is a special case where a user edits his post whereby the topic type got changed by an admin/mod.
 			// Another case would be a mod not having sticky permissions for example but edit permissions.
@@ -1727,7 +1763,6 @@ $page_data = array(
 * @var	bool	preview		Whether or not the post is being previewed
 * @var	bool	save		Whether or not a draft is being saved
 * @var	bool	load		Whether or not a draft is being loaded
-* @var	bool	delete		Whether or not the post is being deleted
 * @var	bool	cancel		Whether or not to cancel the form (returns to
 *				viewtopic or viewforum depending on if the user
 *				is posting a new topic or editing a post)
@@ -1744,6 +1779,7 @@ $page_data = array(
 *		s_topic_icons, form_enctype, s_action, s_hidden_fields,
 *		post_id, topic_id, forum_id, submit, preview, save, load,
 *		delete, cancel, refresh, error, page_data, message_parser
+* @change 3.1.2-RC1 Removed 'delete' var as it does not exist
 */
 $vars = array(
 	'post_data',
@@ -1761,7 +1797,6 @@ $vars = array(
 	'preview',
 	'save',
 	'load',
-	'delete',
 	'cancel',
 	'refresh',
 	'error',
--- a/search.php
+++ b/search.php
@@ -922,7 +922,7 @@ if ($keywords || $author || $author_id || $search_id || $submit)
 				$posts_unapproved = ($row['topic_visibility'] == ITEM_APPROVED && $row['topic_posts_unapproved'] && $auth->acl_get('m_approve', $forum_id)) ? true : false;
 				$topic_deleted = $row['topic_visibility'] == ITEM_DELETED;
 				$u_mcp_queue = ($topic_unapproved || $posts_unapproved) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&amp;mode=' . (($topic_unapproved) ? 'approve_details' : 'unapproved_posts') . "&amp;t=$result_topic_id", true, $user->session_id) : '';
-				$u_mcp_queue = (!$u_mcp_queue && $topic_deleted) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&amp;mode=deleted_topics&amp;t=$result_topic_id", true, $user->session_id) : '';
+				$u_mcp_queue = (!$u_mcp_queue && $topic_deleted) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&amp;mode=deleted_topics&amp;t=$result_topic_id", true, $user->session_id) : $u_mcp_queue;
 
 				$row['topic_title'] = preg_replace('#(?!<.*)(?<!\w)(' . $hilit . ')(?!\w|[^<>]*(?:</s(?:cript|tyle))?>)#is', '<span class="posthilit">$1</span>', $row['topic_title']);
 
--- a/styles/prosilver/style.cfg
+++ b/styles/prosilver/style.cfg
@@ -21,8 +21,8 @@
 # General Information about this style
 name = prosilver
 copyright = © phpBB Limited, 2007
-style_version = 3.1.1
-phpbb_version = 3.1.1
+style_version = 3.1.2
+phpbb_version = 3.1.2
 
 # Defining a different template bitfield
 # template_bitfield = lNg=
--- a/styles/prosilver/template/confirm_delete_body.html
+++ b/styles/prosilver/template/confirm_delete_body.html
@@ -2,6 +2,7 @@
 	<form action="{S_CONFIRM_ACTION}" method="post">
 		<p>{MESSAGE_TEXT}</p>
 
+		<!-- IF not S_SHADOW_TOPICS -->
 		<!-- IF not S_SOFTDELETED and S_ALLOWED_DELETE and S_ALLOWED_SOFTDELETE -->
 			<label>
 				<strong>{L_DELETE_PERMANENTLY}{L_COLON}</strong>
@@ -14,6 +15,7 @@
 			<strong>{L_DELETE_REASON}{L_COLON}</strong><br /><span>{L_DELETE_REASON_EXPLAIN}</span><br />
 			<input type="text" name="delete_reason" value="" class="inputbox autowidth" maxlength="120" size="45" />
 		</label>
+		<!-- ENDIF -->
 
 		<fieldset class="submit-buttons">
 			<input type="button" name="confirm" value="{L_YES}" class="button1" />&nbsp;
@@ -33,6 +35,7 @@
 
 	<p>{MESSAGE_TEXT}</p>
 
+	<!-- IF not S_SHADOW_TOPICS -->
 	<fieldset class="fields1">
 		<!-- IF not S_SOFTDELETED and S_ALLOWED_DELETE and S_ALLOWED_SOFTDELETE -->
 			<dl>
@@ -51,6 +54,7 @@
 			<dd><input type="text" name="delete_reason" id="delete_reason" value="" class="inputbox autowidth" maxlength="120" size="45" /></dd>
 		</dl>
 	</fieldset>
+	<!-- ENDIF -->
 
 	<fieldset class="submit-buttons">
 		{S_HIDDEN_FIELDS}
--- a/styles/prosilver/template/forumlist_body.html
+++ b/styles/prosilver/template/forumlist_body.html
@@ -80,6 +80,8 @@
 						{L_POST_BY_AUTHOR} {forumrow.LAST_POSTER_FULL}
 						<!-- IF not S_IS_BOT --><a href="{forumrow.U_LAST_POST}">{LAST_POST_IMG}</a> <!-- ENDIF --><br />{forumrow.LAST_POST_TIME}<!-- ELSE -->{L_NO_POSTS}<br />&nbsp;<!-- ENDIF --></span>
 					</dd>
+				<!-- ELSE -->
+					<dd>&nbsp;</dd>
 				<!-- ENDIF -->
 			</dl>
 			<!-- EVENT forumlist_body_forum_row_append -->
--- a/styles/prosilver/template/index_body.html
+++ b/styles/prosilver/template/index_body.html
@@ -13,6 +13,8 @@
 
 <!-- INCLUDE forumlist_body.html -->
 
+<!-- EVENT index_body_forumlist_body_after -->
+
 <!-- IF not S_USER_LOGGED_IN and not S_IS_BOT -->
 	<form method="post" action="{S_LOGIN_ACTION}" class="headerspace">
 	<h3><a href="{U_LOGIN_LOGOUT}">{L_LOGIN_LOGOUT}</a><!-- IF S_REGISTER_ENABLED -->&nbsp; &bull; &nbsp;<a href="{U_REGISTER}">{L_REGISTER}</a><!-- ENDIF --></h3>
--- a/styles/prosilver/template/memberlist_email.html
+++ b/styles/prosilver/template/memberlist_email.html
@@ -80,7 +80,7 @@
 		<!-- IF S_REGISTERED_USER -->
 		<dl>
 			<dt>&nbsp;</dt>
-			<dd><label for="cc_email"><input type="checkbox" name="cc_email" id="cc_email" value="1" checked="checked" tabindex="5" /> {L_CC_EMAIL}</label></dd>
+			<dd><label for="cc_sender"><input type="checkbox" name="cc_sender" id="cc_sender" value="1" checked="checked" tabindex="5" /> {L_CC_SENDER}</label></dd>
 		</dl>
 		<!-- ENDIF -->
 		</fieldset>
--- a/styles/prosilver/template/memberlist_search.html
+++ b/styles/prosilver/template/memberlist_search.html
@@ -6,6 +6,7 @@
 
 	<p>{L_FIND_USERNAME_EXPLAIN}</p>
 
+	<!-- EVENT memberlist_search_fields_before -->
 	<fieldset class="fields1 column1">
 	<dl style="overflow: visible;">
 		<dt><label for="username">{L_USERNAME}{L_COLON}</label></dt>
@@ -39,6 +40,7 @@
 		<dt><label for="search_group_id">{L_GROUP}{L_COLON}</label></dt>
 		<dd><select name="search_group_id" id="search_group_id">{S_GROUP_SELECT}</select></dd>
 	</dl>
+	<!-- EVENT memberlist_search_sorting_options_before -->
 	<dl>
 		<dt><label for="sk" class="label3">{L_SORT_BY}{L_COLON}</label></dt>
 		<dd><select name="sk" id="sk">{S_SORT_OPTIONS}</select> <select name="sd">{S_ORDER_SELECT}</select></dd>
@@ -66,6 +68,7 @@
 		<dd><input class="inputbox medium" type="text" name="ip" id="ip" value="{IP}" /></dd>
 	</dl>
 <!-- ENDIF -->
+	<!-- EVENT memberlist_search_fields_after -->
 	</fieldset>
 
 	<div class="clear"></div>
--- a/styles/prosilver/template/posting_buttons.html
+++ b/styles/prosilver/template/posting_buttons.html
@@ -92,6 +92,9 @@
 		<!-- ENDIF -->
 	</select>
 	<input type="button" class="button2 bbcode-color" name="bbpalette" id="bbpalette" value="{L_FONT_COLOR}" onclick="change_palette();" title="{L_BBCODE_S_HELP}" />
+
+	<!-- EVENT posting_editor_buttons_custom_tags_before -->
+
 	<!-- BEGIN custom_tags -->
 		<input type="button" class="button2 bbcode-{custom_tags.BBCODE_TAG_CLEAN}" name="addbbcode{custom_tags.BBCODE_ID}" value="{custom_tags.BBCODE_TAG}" onclick="bbstyle({custom_tags.BBCODE_ID})" title="{custom_tags.BBCODE_HELPLINE}" />
 	<!-- END custom_tags -->
--- a/styles/prosilver/template/ucp_pm_viewmessage.html
+++ b/styles/prosilver/template/ucp_pm_viewmessage.html
@@ -29,7 +29,7 @@
 
 		<!-- IF RANK_TITLE or RANK_IMG --><dd class="profile-rank">{RANK_TITLE}<!-- IF RANK_TITLE and RANK_IMG --><br /><!-- ENDIF -->{RANK_IMG}</dd><!-- ENDIF -->
 
-		<dd class="profile-posts"><strong>{L_POSTS}{L_COLON}</strong> {AUTHOR_POSTS}</dd>
+		<dd class="profile-posts"><strong>{L_POSTS}{L_COLON}</strong> <!-- IF U_AUTHOR_POSTS != '' --><a href="{U_AUTHOR_POSTS}">{AUTHOR_POSTS}</a><!-- ELSE -->{AUTHOR_POSTS}<!-- ENDIF --></dd>
 		<!-- IF AUTHOR_JOINED --><dd class="profile-joined"><strong>{L_JOINED}{L_COLON}</strong> {AUTHOR_JOINED}</dd><!-- ENDIF -->
 
 		<!-- EVENT ucp_pm_viewmessage_custom_fields_before -->
--- a/styles/prosilver/theme/common.css
+++ b/styles/prosilver/theme/common.css
@@ -601,7 +601,7 @@ ul.linklist.bulletin > li.no-bulletin:before {
 }
 
 .breadcrumbs .crumb:before {
-	content: '‹';
+	content: '\2039';
 	font-weight: bold;
 	padding: 0 0.5em;
 }
--- a/styles/prosilver/theme/content.css
+++ b/styles/prosilver/theme/content.css
@@ -497,7 +497,7 @@ blockquote.uncited {
 	text-transform: uppercase;
 	border-bottom: 1px solid transparent;
 	margin-bottom: 3px;
-	font-size: 0.8em;
+	font-size: 0.8em !important;
 	font-weight: bold;
 	display: block;
 }
--- a/styles/prosilver/theme/cp.css
+++ b/styles/prosilver/theme/cp.css
@@ -59,10 +59,6 @@ ul.cplist {
 	border-bottom: none;
 }
 
-#cp-main .postbody p {
-	font-size: 1.1em;
-}
-
 #cp-main .pm-message {
 	border: 1px solid transparent;
 	margin: 10px 0;
@@ -78,6 +74,10 @@ ul.cplist {
 	margin-top: 0;
 }
 
+.panel-container .postbody p.author {
+	font-size: 1.1em;
+}
+
 #cp-main .buttons {
 	margin-left: 0;
 }
--- a/vendor/autoload.php
+++ b/vendor/autoload.php
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer' . '/autoload_real.php';
 
-return ComposerAutoloaderInit40a0154fc4f0ec1af932b1e7e0f76452::getLoader();
+return ComposerAutoloaderInit58d8016f7f5f9cd7a487982dd1188ea9::getLoader();
--- a/vendor/composer/autoload_real.php
+++ b/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit40a0154fc4f0ec1af932b1e7e0f76452
+class ComposerAutoloaderInit58d8016f7f5f9cd7a487982dd1188ea9
 {
     private static $loader;
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInit40a0154fc4f0ec1af932b1e7e0f76452
             return self::$loader;
         }
 
-        spl_autoload_register(array('ComposerAutoloaderInit40a0154fc4f0ec1af932b1e7e0f76452', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit58d8016f7f5f9cd7a487982dd1188ea9', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        spl_autoload_unregister(array('ComposerAutoloaderInit40a0154fc4f0ec1af932b1e7e0f76452', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit58d8016f7f5f9cd7a487982dd1188ea9', 'loadClassLoader'));
 
         $vendorDir = dirname(__DIR__);
         $baseDir = dirname($vendorDir);
--- a/viewforum.php
+++ b/viewforum.php
@@ -504,8 +504,8 @@ if ($start > $topics_count / 2)
 	// Select the sort order
 	$direction = (($sort_dir == 'd') ? 'ASC' : 'DESC');
 
-	$sql_limit = $pagination->reverse_limit($start, $sql_limit, $topics_count);
-	$sql_start = $pagination->reverse_start($start, $sql_limit, $topics_count);
+	$sql_limit = $pagination->reverse_limit($start, $sql_limit, $topics_count - sizeof($announcement_list));
+	$sql_start = $pagination->reverse_start($start, $sql_limit, $topics_count - sizeof($announcement_list));
 }
 else
 {
@@ -694,10 +694,10 @@ if ($s_display_active)
 
 // We need to remove the global announcements from the forums total topic count,
 // otherwise the number is different from the one on the forum list
-$total_topic_count = $topics_count - sizeof($global_announce_forums);
+$total_topic_count = $topics_count - sizeof($announcement_list);
 
 $base_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . ((strlen($u_sort_param)) ? "&amp;$u_sort_param" : ''));
-$pagination->generate_template_pagination($base_url, 'pagination', 'start', $topics_count, $config['topics_per_page'], $start);
+$pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_topic_count, $config['topics_per_page'], $start);
 
 $template->assign_vars(array(
 	'TOTAL_TOPICS'	=> ($s_display_active) ? false : $user->lang('VIEW_FORUM_TOPICS', (int) $total_topic_count),
--- a/viewtopic.php
+++ b/viewtopic.php
@@ -616,7 +616,9 @@ $base_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&amp;t=
 * @var	int		topic_id			Topic ID
 * @var	array	topic_tracking_info	Array with topic tracking data
 * @var	int		total_posts			Topic total posts count
+* @var	string	viewtopic_url		URL to the topic page
 * @since 3.1.0-RC4
+* @change 3.1.2-RC1 Added viewtopic_url
 */
 $vars = array(
 	'base_url',
@@ -628,6 +630,7 @@ $vars = array(
 	'topic_id',
 	'topic_tracking_info',
 	'total_posts',
+	'viewtopic_url',
 );
 extract($phpbb_dispatcher->trigger_event('core.viewtopic_assign_template_vars_before', compact($vars)));
 
@@ -1247,7 +1250,7 @@ while ($row = $db->sql_fetchrow($result))
 				'contact_user' 		=> $user->lang('CONTACT_USER', get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['username'])),
 
 				'online'		=> false,
-				'jabber'		=> ($row['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&amp;action=jabber&amp;u=$poster_id") : '',
+				'jabber'		=> ($config['jab_enable'] && $row['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&amp;action=jabber&amp;u=$poster_id") : '',
 				'search'		=> ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$poster_id&amp;sr=posts") : '',
 
 				'author_full'		=> get_username_string('full', $poster_id, $row['username'], $row['user_colour']),
